Kurs Assemblera cz. 13

 

Drukarko – Drukuj! Stacjo – Stacjonuj!

1. Rewolucyjna czujność

W odpowiedzi na moje zapytanie z 11 odcinka kursu asemblera, wielu z Was wykazało rewolucyjną czujność i poprzysyłało zamówienia na dokładniejsze wytłumaczenie paru procedur kernalowskich. Większość listów obracało się wokół tematyki połączeń z urządzeniami zewnętrznymi, takimi jak stacja dysków i drukarka (bo tak Bogiem a prawdą, tylko z nich może wycisnąć coś sensownego zwyczajny użytkownik złomodorka). Jak mawiają demokraci, vox populi – vox Dei, więc najpierw zajmiemy się zagadnieniem poruszanym zdecydowanie częściej.

A ogólniej – jak kazać stacji dysków wykonać jakiekolwiek, choćby najprostsze polecenie DOS-u? Wielu, co prawda, ma z tym problemy nawet z poziomu BASIC-a, ale to początkujący. My jesteśmy zaawansowani i BASIC mamy w małym paluszku. Dlatego też wiemy, że trzeba wpisać komputerowi takie instrukcje:

10 OPEN 15,8,15
20 PRINT# 15,”N:NAZWA DYSKU,ID”
30 CLOSE 15

Mamy tylko pewne wątpliwości, jak zrobić to w asemblerze. Na początek powiem Wam, że trzeba wystać stacji dokładnie takie same komendy niezależnie od tego, czy pracujemy w asemblerze, czy w BASIC-u. Stacji jest po prostu wszystko jedno, ona tego nie widzi. Dostaje tylko po drucie bajty, które potem układa sobie w rządku i patrzy, czy wynika z nich coś sensownego. Jeśli nie, to zamiast formatować dysk zacznie wesoło mrugać diodką. Musimy więc po prostu starać się jak najdokładniej odwzorować instrukcje BASIC-a w rozkazach asemblera.

Pierwszym, najbardziej oczywistym jest OPEN ($ffc0), o którym wiemy jednak, że nie może istnieć bez uprzedniego SETLFS ($ffba). Potem ustawiamy odpowiedni kanał jako aktualny kanał wyjściowy, procedurą CHKOUT ($ffc9). Kolejne bajty posyłamy na szynę znanym dobrze CHROUT ($ffd2). Kiedy już poślemy wszystko, trzeba dla porządku ustawić monitor i klawiaturę jako aktualne urządzenia wejścia/wyjścia (CLRCHN, $ffcc). W zasadzie należałoby jeszcze zamknąć kanał – CLOSE ($ffc3), ale przedłużyłoby to czas pracy programu, który musiałby oczekiwać, aż stacja ogłosi gotowość do dalszej pracy. Teraz musimy jeszcze powpisywać różne cyferki do rejestrów A, X i Y i program gotowy. A oto (oczywiście tylko dla porównania z Waszymi osiągnięciami!) moje rozwiązanie palącego problemu formatowania dyskietki.

I w ten oto prosty sposób robimy w 19 linijkach kodu to, co w BASIC-u zajmuje trzy linie. Oczywiście fanatycy BASIC-a mogą to wykorzystać jako argument w ew. dyskusji, ale my skwitujemy to pogardliwym uśmieszkiem. A tak poważnie – to procedura taka może się okazać bardzo użyteczna, jeżeli projektujemy napisanie jakiegoś programu współpracującego ze stacją dysków. Np. możemy wstawić doń opcję „Operacja DOS”, pobrać dane z klawiatury i po prostu wysłać je potem do stacji. Każdy program dużo na tym zyska. Trzeba by tylko zmienić pętlę wysyłającą znaki do stacji. Pętla ta znajduje się w komórkach $27220-$272e.

3. Jak by tu coś wydrukować?

Kłopot bardzo podobny do wyżej poruszonego mają właściciele drukarek. Nie bardzo bowiem wiedzą, jak zmusić je do drukowania czegokolwiek poza tekstem z edytorów tekstu. A w istocie problem sprowadza się do tego samego, co poprzedni – do wysłania odpowiednich bajtów po odpowiednim drucie. No i właściwego ich zaadresowania. Przy okazji udowodnię, że dla nas, asemblerowców nie są staszne nawet znaki sterujące (będą one właściwe dla MPS803 i innych, potrafiących tąż emulować). Podczas drukowania należy pamiętać o zakończeniu ostatniej linijki kodem 13, odpowiadającym naciśnięciu RETURN. Inaczej drukarka będzie czekać aż do zapełnienia bufora, czyli zazwyczaj do świętego Nigdy.

Warto jeszcze nadmienić, że w wersji asemblerowej dla wygody wprowadziłem etykietę „urz”. Oznacza ona numer urządzenia, który przypisany jest drukarce. Bywa 5, bywa 4. U mnie akurat było 4.

Kurs Assemblera cz. 12

 

Stacja dysków i bity na drutach

1. Witajcie!

Do tej pory nasze kontakty ze stacją dysków były nieco, rzekłbym, monotonne. Ograniczaliśmy się do ładowania i nagrywania programów. No i – ma się rozumieć – gier. Nadszedł jednak czas, by szczerze sobie powiedzieć, że to nie wystarczy. Jesteśmy już duzi, asemblera uczymy się już dość długo, pora więc na wzięcie stacji – jak to mawiali przedwojenni majstrowie – na warsztat.

2. Zaczynamy.

Na początek sprobujmy wtadować katalog dyskietki. Zgodnie z tym, czego do tej pory się nauczyliście, powinniście już wiedzieć, jak to zrobić. Proste BASIC-owe LOAD „$”,8 musimy zamienić na co najmniej trzy odwołania do procedurek KERNALa. Uwaga! Sięgnij teraz po sierpniowy numer „C&A” i otwórz go na stronie 26. Najpierw musimy ustawić wskaźniki pliku logicznego, czyli jego numer wstawić do akumulatora (niech będzie to 1), numer urządzenia – do X (stacja dysków – to 8), a adres pomocniczy – do Y (nie korzystamy z niego – wstawiamy $ff). Po takim ustawieniu rejestrów uruchamiamy procedurę SETLFS ($ffba). Gdy ładujemy katalog, musimy podać jego nazwę, czyli „$”. Wartość odpowiadającą dolarowi, czyli 68/$24 wstawiamy gdzieś do pamięci, pod etykietą TYT (TYT od TYTuł, ale w skrócie, żeby oszczędzić Wam pisania). Adres rozbijamy na młodszy i starszy bajt. Bajt młodszy wędruje do rejestru X, starszy zaś – do Y. Do akumulatora wpisujemy 1, bo tytuł składa się tylko z jednego znaku – $ zresztą. Po czym wskakujemy do procedurki SETNAM ($ffbd). W tym właśnie momencie

3. Zaczynają się schody

bo gdybyśmy kazali katalog ordynarnie załadować to nie dość, że nie byłoby to żadne odkrycie, ale na domiar złego zostałby zlikwidowany istniejący w pamięci program w BASIC-u. A sama asemblerowa procedura ładująca tylko nieznacznie różniłaby się od programu 2 z odcinka sierpniowego. Ale przecież to nie my jesteśmy niewolnikami swoich komputerów, ale one mają tańczyć tak, jak my im zagramy. Na czym bowiem polega ładowanie jakiegokolwiek zbioru do pamięci? Komputer musi otworzyć zbiór, a następnie pobierać z niego kolejne bajty i wstawiać je do pamięci, do kolejnych komórek. Oczywiste? Te czynności wykonuje KERNALowa procedura LOAD($ffd5).

My zaś musimy zbiór otworzyć, a kolejne bajty nie – zapamiętywać jako program BASIC, a jedynie wyrzucać na ekran jako dane tymczasowe. Jak jednak dokonać tego w praktyce? Po przejrzeniu tabeli KERNALa widzimy, że do przygotowania pliku do wzięcia udziału w operacjach we/wy służy procedura OPEN ($ffc0). Wymaga ona tylko, by numer tego pliku logicznego znalazł się w akumulatorze. I znajdzie się. Musimy tylko uważać, by był identyczny z numerem, który podaliśmy w rejestrze A przed wywołaniem procedury SETLFS.

Ale to jeszcze nie wszystko. Po otwarciu pliku musimy ustawić go jako aktualny kanał wejściowy. Służy do tego CHKIN. Dla odmiany, numer pliku musi się znaleźć w rejestrze X.
Teraz rzecz jest już w zasadzie prosta. Po drucie ze stacji musimy przeciągnąć bajty procedurą CHRIN ($ffcf). Kolejny znak znajdzie się w akumulatorze po każdym wywołaniu CHRIN. Aby wartości z akumulatora nie szły nam w komin, to po każdym CHRIN wykonać musimy CHROUT ($ffd2). Co jest znanym już od dawna sposobem na wydrukowanie znaku na ekranie. A oto przecież chodzi

4. Schody się piętrzą

Jeśli jednak nakażemy komputerowi powtarzanie prostej pętli CHRIN/CHROUT, to nie dziwmy się, jeśli otrzymamy sieczkę zamiast uczciwego katalogu dyskietki. Aby prześledzić, jak wygląda struktura katalogu popełniłem prosty programik, który po wydrukowaniu każdej litery pokazywał na ekranie jej wartość liczbową (czyli kod ASCII, a właściwie PETASCII), następnie czekał na naciśnięcie jakiegokolwiek klawisza, a potem przechodził z powrotem do CHRIN. Program taki jest prościutki i każdy z Was powinien już umieć napisać go samemu, więc nie będę tracił na niego bądź co bądź cennego miejsca. Włożyłem do stacji pierwszą z brzegu dyskietkę i otrzymałem co następuje:

5. Rozpiska bajtów w katalogu dysku

Oczywiście, ta rozpiska jest tylko przykładowa, ale ma wszystkie elementy: nagłówek, nazwę jakiegoś programu i zakończenie w postaci liczby wolnych bloków. Jak widać, są podane w takiej samej formie jak program w BASIC-u z tą różnicą, że nie ma adresów początków następnych linii na początku poprzednich. Projektanci maszyny zdawali sobie sprawę, że nie będą one nikomu do niczego potrzebne, więc poszli na łatwiznę i powpisywali tam po prostu jedynki. A skutek tego taki, że te nieszczęsne jedynki musimy tylko w katalogu pobrać i puścić w powietrze.

Po jedynkach idzie liczba podana w postaci młodszego i starszego bajtu. Do wydrukowania na ekranie liczby w zwykłej, dziesiętnej postaci, gdy mamy ją jako dwa bajty, służy umieszczona w pamięci ROM procedura LINPRT. Normalnie przydaje się w BASIC-u podczas drukowania listingów programów, ale my, cwaniaczki, wykorzystamy ją do własnych celów.

Żeby wykorzystać LINPRT ($bdcd) trzeba jej podać młodszy bajt liczby w rejestrze X, a starszy – w akumulatorze. Następnie idzie nazwa. Trzeba ją bajt po bajcie drukować tak długo, aż natkniemy się na zero. Gdy dogrzebiemy się do zera, to wyrzucamy na ekran 13/$Od i przechodzimy do następnej linii.

Wszystko ładnie, ale tak napisany program, niestety, nie będzie działał poprawnie. To znaczy – na pierwszy rzut oka tak, ale nie rozpozna, że katalog się skończył i to, co zostało wydrukowane z prędkością światła ucieknie nam z pola widzenia. Drugim poważnym mankamentem jest fakt, że programu tak napisanego nie można zatrzymać klawiszem RUN/STOP. Nie sprawia to kłopotu, gdy programów jest na dysku kilka, ale gdy zajmują one trzy ekrany, zaczyna się robić kłopotliwe. Oba te problemy są, oczywiście, pozorne i rozwiązać je można w minutę dziesięć. Wskaźnikiem końca pliku jest szósty bit w komórce STATUS ($90). Jeżeli zostanie on zapalony, czyli jeżeli wykonanie AND #%01000000 z wartością z rejestru STATUS da wynik 1, to znaczy to, że dalszy odczyt z kanału nie ma sensu, bo wpuścimy się tylko w jakiś podejrzany kanał organizacyjny i mogą z tego wyniknąć jakieś nieprzyjemności. Do obsługi klawisza STOP służy zaś rejestr STKEY ($91). Naciśnięcie tego guzika spowoduje zapalenie ostatniego, siódmego bitu tej komórki. Dodatkową zaletą tego rozwiązania jest fakt, że testowanie stopu robimy za pomocą jednego tylko rozkazu: BPL. Jeżeli STOP będzie wciśnięty, skok zostanie wykonany. Jasne? Jasne?
Warto jeszcze nadmienić, że po skończeniu (lub przerwaniu, co na jedno wychodzi) odczytywania katalogu dyskietki należy zamknąć plik wejściowy, ten który przed odczytem otwieraliśmy oraz – dla porządku – ustawić monitor i klawiaturę jako aktualne we/wy. Służy do tego CLOSE ($ffc3) i CLRCHN ($ffcc). I w ten sposób dobrnęliśmy do szczęśliwego finału. Teraz, za cierpliwe zczytanie całego odcinka należy się Wam

6. Listing

7. NA ZRAZIE

Listing nagrajcie sobie na dysk, ewentualnie na taśmę. Magnetofoniaków przepraszam, że ten odcinek nie byt dla nich. Takie jest życie, a cukier drożeje. Uruchamia się jak zwykle – SYS 10000. Nie uschnijcie z tęsknoty.

Kurs Assemblera cz. 11

 

KERNAL

1. Retrospekcje

Czy pamiętacie jak w odcinku VII naszego kursu, w styczniu tego roku bawiliśmy się w drukowanie na ekranie literki „A” za pomocą rozkazu JSR? I to JSR w jakieś dziwaczne miejsce, w którym nic nie wstawialiśmy, do komórki $ffd2, gdzieś, w odległe peryferia pamięci. Mimo wszystko, literka „A” pojawiła się na ekranie. Pisałem wtedy, że tam właśnie mieści się procedurka ROM, która powoduje przeniesienie wartości akumulatora do urządzenia odbierającego. A był nim akurat ekran, więc wszystko poszło po naszej myśli. Trzeba nam wiedzieć, że $ffd2 to jedna z pozycji tabeli skoków zwanej KERNAL.

2. KERNAL – wiek dziecinny.

Nasz dzisiejszy bohater to tablica skoków. Kilkadziesiąt komórek pamięci zajętych jest sekwencjami w rodzaju: JMP ($0326), JMP $f49e, JMP $ff5d, JMP $f6e4 i podobnymi. Są to odwołania do różnych procedur zainstalowanych w ROM: LOAD, SAVE, otwarcie pliku itd.

Kiedy powstał KERNAL, trudno mi dokładnie powiedzieć. Jego podstawowym założeniem była mrzonka o kompatybilności wszystkich komputerów firmy Commodore. Każdy z nich miał bowiem mieć taki sam KERNAL. Niezależnie od tego, jak prezentowałyby się inne części ROM, zawsze JSR $ffd2 powodować miało wydrukowanie znaku. Dzięki temu miała zostać osiągnięta pełna zgodność. I rzeczywiście, działało to w dawnych biurowych maszynach (takich staromodnych, w których monitor, stacja i klawiatura były w jednej obudowie). Zdawało egzamin, gdy chodziło wyłącznie o nudne programy biurowe, obsługujące w zasadzie wyłącznie wejścia, wyjścia i pamięć.

Już kiedy pojawił się VIC-20, a potem C-64 ze swoimi niesamowitymi możliwościami, przestano w zasadzie marzyć o możliwości wczytywania programów z jednego komputera na drugim. Zwłaszcza, kiedy różni zapaleńcy zaczęli zabawiać się z rastrem, samplingiem i innymi dziwacznymi efektami, które na starych Commodorach były do osiągnięcia. KERNAL jednak pozostał, bo okazało się po prostu, że jest skutecznym i wygodnym narzędziem do pisania programów. Bowiem daje to wygodę, że – jeśli chcemy np. wczytać z dysku program – nie musimy wertować całej mapy pamięci w nadziei odnalezienia czegoś ciekawego, a wystarczy nam jej ostatnia część – KERNAL.

3. Pełna rozpiska.

A teraz czas na pełną rozpiskę tabeli skoków dla tych wszystkich, którzy mapy pamięci nie mają oraz dla tych, którzy wprawdzie ją mają, ale „gdzieś ją położyli”, a chcieli by mieć KERNAL oprawiony w ramki i powieszony nad łóżkiem. Podaję tutaj po kolei: adres pozycji w systemie szesnastkowym, dziesiętnym, skrót nazwy procedury oraz krótki jej opis.

4. Parę słów.

Fakt, że umieściłem pełną rozpiskę tablicy KERNAL w kursie programowania nie oznacza wcale – broń Boże! – że trzeba się jej uczyć na pamięć. Wręcz przeciwnie – ma służyć jako ściągawka z KERNALa, która może się przydać nawet doświadczonym programistom. Pewne procedury i tak wejdą w nawyk (np. LOAD, SETLFS) a pewnych i tak nikt nigdy nie używa (np. SCREEN, IOBASE).

A teraz pokażę Wam, jak korzystać z tej tablicy. Oto kilka przykładowych procedur, które nie raz i nie dwa razy mogą się przydać, czego dowodem są liczne listy do redakcji.

5. Save.

Oto przykładowa procedura, która nagra nam na dysk zawartość ekranu, pod tytułem „c&a”.

Jeśli w drugiej linijce programu LDX #$08 zmienimy na LDX #$01, to nagramy wszystko na taśmę a nie na dyskietkę.

6. Load.

Jeśli przyjdzie wam chętka władować cokolwiek do pamięci, możemy się posłużyć tym oto programem (tu akurat ładujemy nagrany właśnie plik „c&a”, w lokacje od $1000 wzwyż).

Ciekawscy i niedowiarkowie powinni sprawdzić monitorem pamięć od $1000 do $13e8. Będzie tam ekran dokładnie taki, jak podczas wywołania procedury SAVE.

7. Input.

Prościutki jest też programik, który posłuży nam jako asemblerowy odpowiednik BASIC-owego polecenia INPUT:

Dzięki niemu, oprócz niewątpliwej radości jaką sprawi nam zobaczenie wstukanemu własnoręcznie tekstu w pamięci, ucieszy nas też pewnie, że wiemy, ile właściwie ma on liter.

8. Pa Pa!

Na ten miesiąc chyba wystarczy już natłoku informacji, więc będę kończył, żeby Was więcej nie przemęczać. Spróbujcie może zrobić coś sensownego z tym, co Wam tu podałem. Jeśli interesuje Was, jak wykorzystać inne procedury KERNAL to napiszcie, a przygotuję kolejny artykuł na ten arcyciekawy temat. Pa pa!

Kurs Assemblera cz. 10

 

KIEDY DWA PLUS DWA RÓWNA SIĘ DWA…

Nielogiczny tytuł? Wręcz przeciwnie! Tym razem będziemy mówić właśnie o logice i operacjach logicznych. Najpierw jednak napiszemy program, który przedstawi nam zawartość jednego bajtu, niech będzie to bajt zawarty w akumulatorze, w postaci zer i jedynek (mogę to być literki a i b, gwiazdki i kropki, kółka białe i kółka czarne – co tylko chcecie). Zera te i jedynki niech wyświetli gdziekolwiek na ekranie. Na ten przykład w lewym górnym rogu. Albo na środku. Jak zrobimy taki program? Musimy po kolei sprawdzić wszystkie bity bajtu i zależnie od tego wydrukować na ekranie odpowiednie znaki. Można to zrobić np. za pomocą instrukcji ROL lub ROR, przerolowując cacy bajt w jedną stronę. Dzięki temu za każdym razem w C otrzymamy wiadomość, jaka była zawartość kolejnego bitu. Jeśli nie pamiętacie szczegółów, odsyłam do numeru listopadowego, AD 1992. O przesuwaniu bajtów powiedziałem tam wystarczająco dużo.

 

BIT PO BICIE

Tym razem jednak wykorzystamy inny rozkaz, tak rzadko wydawany, że zrobiło mi się go żal i postanowiłem wykorzystać go we właściwym dla niego celu. Ów nieszczęśnik nazywa się BIT i zazwyczaj służy do cyklowania rastrów. Głównie dlatego, że nic konkretnego nie robi, a zajmuje dwa lub trzy cykle (a trzeba Wam wiedzieć, że takie instrukcje to nie lada gratka podczas intensywnego cyklowania). Oczywiście jednak umieszczanie w tak ascetycznym procesorze dwóch instrukcji, które nic nie robią (ta i NOP) byłoby rozrzutnością. Coś więc jednak BIT robi. Wykonuje mianowicie operację nazywaną iloczynem logicznym a służy do sprawdzania poszczególnych bitów (ang. Bit Testing). BIT można wykonywać jedynie na komórkach pamięci i to bez żadnego indeksowania (nie ma tu ani ,X ani ,Y). Sam wynik testowania nie jest nigdzie zapisany, a do dyspozycji mamy jedynie wskaźnik Zero (który – jak wiadomo – ustawia się na 1, kiedy wynikiem ostatniej operacji było właśnie zero), nieśmiertelne Negative (liczby ujemne) i oVerflow (przepełnienie). Do N kopiowany jest siódmy (najstarszy) bit sprawdzanej komórki pamięci, a do V – bit szósty. Rozkaz ten sprawdza wynik iloczynu logicznego zawartości akumulatora i komórki pamięci.

 

ILOCZYN LOGICZNY

Nie wiecie, co to jest iloczyn logiczny, tak? To bardzo proste, a najłatwiej wyjaśnić to na liczbach dwójkowych. Iloczyn logiczny 1 i 1 wynosi 1. Dla 1 i 0 wynikiem jest 0, dla 0 i 0 – też zero. Zasadniczo więc przy jednej liczbie, równej jeden lub zero, wynik iloczynu logicznego jest taki sam, jak zwykłego iloczynu:

Jasne? Przy sprawdzaniu iloczynu dwóch liczb dłuższych niż jednobitowe, operacji tej ulegają wszystkie bity po kolei. Na wyjściu zaś jedynki mamy tylko tam, gdzie w dwóch poprzednich liczbach mieliśmy bity zapalone. Na przykładzie:

W najniższym bajcie mamy wynik iloczynu logicznego dwóch poprzednich liczb. Jedynki, czyli zapalone bity, są tylko na tych pozycjach, na których w OBU poprzednich mieliśmy po jedynce. Jasne? Jasne? (Jak mawiał jeden z Vonnegutowskich bohaterów). W sumie jednak cały ten wynik nie jest nigdzie przechowywany. Komputer interesuje się tylko, czy na wyjściu była chociaż jedna jedynka. Jeśli tak, Z pozostanie ciemne.

Teraz więc napisz program, który po kolei przetestuje wszystkie bity wchodzące w skład bajtu przechowywanego w akumulatorze. Wynik niech będzie przedstawiony w jakikolwiek, byle czytelny, sposób. Wynik działania swojego programu proponuję porównać z moim, przykładowym. Program ten przyda się nam na później, żeby w praktyce przyjrzeć się działaniu pozostałych działań logicznych. Jeszcze jedna uwaga techniczna: przyjmijmy, że program zacznie się od $2800.

 

BO W TYM CAŁY JEST AMBARAS…

Mając teraz przygotowany dobrze grunt, możemy zaczynać na dobre (no, i na złe też!) zabawy z operacjami logicznymi. Pierwszą z nich niech będzie AND, czyli „i”, czyli iloczyn logiczny. Jak TO działa, z grubsza już wiemy. Najważniejszą różnicą między AND a BIT jest to, że wynik AND nie ulatnia się w przestrzeń kosmiczną (jak to się dzieje w przypadku drugiego rozkazu), lecz jest uczciwie zapisywany w akumulatorze, byśmy mogli zrobić z nim wszystko, co tylko przyjdzie nam do głów. Odpowiednio do wyniku operacji zmienia się wskaźnik Z, zaś siódmy bit wyniku ląduje w N. Zobaczmy więc, co NAPRAWDĘ stanie się, gdy wykonamy operację %00111111 AND %11111000.

Jeśli masz w pamięci BIT – TESTER, uruchom ten króciutki programik przez SYS 10000 / G 2710. No i co widzimy? (Widzimy oczywiście wynik 00111000, chyba, że klniemy przed czarnym monitorem, bo elektrowni akurat zachciało się wyłączyć prąd). Mamy oto ładny fakt na poparcie moich wywodów o BIT i AND. Ale iloczyn to nie jedyna znana matematykom i 6502 operacja logiczna.

 

JEST JESZCZE SUMA

…czyli ta operacja, która już niedługo pokaże nam, że tytuł z tego miesiąca ma jakie takie resztki sensu. Suma logiczna to operacja OR, czyli LUB. W 6502 odpowiada jej rozkaz ORA (OR Accumulator). LUB, podobnie jak I, potrzebuje na wejściu dwóch liczb, z których na wyjściu robi jedną. I tak:

Czyli jeśli jedna LUB druga liczba jest jedynką, to wyjście też się nią stanie. A jak będzie z całymi bajtami? Spróbujmy wyświetlić sobie sumę 2 i 2 (logiczną sumę, of course (jak mawiają Anglicy)):

I co dostaliśmy jako wynik? %00000010, czyli – po przeniesieniu na system dziesiętny – 2. I bardzo dobrze.

 

W RÓŻNOŚCI SIŁA

Trzecim rozkazem, jaki dziś poznamy będzie EOR, co tłumaczy się jak Exclusive OR, zaś po naszemu mówią na to suma modulo 2. Ma ona to do siebie, że wstawia jedynki tam na wyjściu, gdzie na wejściu była dokładnie jedna jedynka. Jeśli oba wejścia byty takie same (dwa zera albo dwie jedynki) to na wyjściu pojawi się zero.

Wygląda to tak:
1 eor 0 = 1
O eor 1 = 1
O eor O = 0
1 eor 1 = 0

Zgodnie z moimi doświadczeniami Wam też powinno wyjść %10010001.

 

NOT, CZYLI NEGACJA

Asembler 6502 nie ma bezpośrednio rozkazu NOT, który normalnie służy do zmiany bitów na ich przeciwieństwa:
not 1 = 0
not 0 = 1.

Można jednak zasymulować NOT za pomocą EOR, zwyczajnie wstawiając $ff jako maskę, ot tak:

Teraz znajome SYS 10000 lub G 2710 i wynikiem jest… %11000001. Czyli to, czego spodziewaliśmy się po negacji: zgaszone zostało to, co było zapalone, a ogień przeniósł się na to, co było zimne. I to już wszystkie operacje logiczne i jednocześnie wszystko, co przygotowałem dla Was w tym miesiącu. Trzymajta się!

AND z ORem w klapie

 

W tym miesiącu poznaliśmy rozkazy:

AND – wykonanie iloczynu logicznego na zawartości akumulatora oraz liczbie lub zawartości dowolnej komórki. Wynik zapamiętany w akumulatorze

BIT – wykonanie iloczynu logicznego na zawartości akumulatora oraz zawartości dowolnej komórki. Wynik nigdzie nie zapamiętywany.

EOR – wykonanie sumy modulo 2 na zawartości akumulatora oraz liczbie lub zawartości dowolnej komórki. Wynik zapamiętywany w akumulatorze.

ORA – wykonanie sumy logicznej na zawartości akumulatora oraz liczbie lub zawartości dowolnej komórki. Wynik zapamiętywany w akumulatorze.

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.

Kurs Assemblera cz. 8

O SKUTECZNYM PRZERWAŃ SPOSOBIE

Komputer podczas pracy jest podobny do… bo ja wiem, bibliotekarza? Sekretarki? Kancelisty? W każdym razie do kogoś wykonującego zawód, który nie wymaga zbyt wiele twórczego myślenia. Oczywiście – jakiekolwiek tego typu porównanie będzie z definicji nieścisłe. Każde, nawet najbardziej nudne zajęcie wymaga czasem choć odrobiny myślenia twórczego. Na przykład bibliotekarz – wydawałoby się, że tylko przyjmuje, wypożycza i układa na półkach książki. I to wszystko. Ale co zdarzy się, gdy do biblioteki wpadnie groźny szaleniec z odbezpieczonym kałaszem? Komputer na stanowisku bibliotekarza zapewne spytałby delikwenta, czy może potrzebna mu książka na temat militariów z byłego Związku Sowieckiego. Człowiek zaś musiałby coś wykombinować, żeby jakoś się z tej sytuacji wyplątać (niewykluczone zresztą, że mimo wszystko nie znalazłby żadnego rozsądnego wyjścia, ale to nie należy do sprawy).

No dobrze, przejdźmy teraz do właściwej treści dzisiejszego odcinka, tj. do przerwań. Wiemy, że każdą, wykonywaną przez człowieka pracę można PRZERWAĆ. Wyobraź sobie, że odrabiasz właśnie lekcje, klnąc cicho profesorów za zbyt wiele zadań. Nagle dzwoni telefon. Nikogo innego w mieszkaniu nie ma. Co więc robisz? Z ulgą PRZERYWASZ pracę i przystępujesz do omówienia kwestii pójścia do kina na MCMLXXVI część Akademii Policyjnej. Gdy już dokładnie ustalisz szczegóły, odkładasz słuchawkę, POWRACASZ Z PRZERWANIA i kontynuujesz przygotowania do obrony pracy domowej.

W podobny sposób działa Twój komputer. Jego praca jest stosunkowo często PRZERYWANA (z reguły co 1/50 sekundy). Podczas takiej przerwy komputer zwykle sprawdza, czy ktoś aby nie nacisnął któregoś z klawiszy, mruga kursorem, przesuwa zegar i robi inne, podobnie fascynujące rzeczy. Po wykonaniu tych wszystkich arcyciekawych czynności POWRACA do programu, który wykonywał poprzednio. I wszystko gra, jak w orkiestrze strażackiej.

Znaleźli się jednak cwaniacy, którym taki obraz działania wszechświata nie do końca wystarczał. Mówili: „Przecież ten kursor nie może tak migać, zróbmy coś!” Albo: „Dlaczego klawisze nie pikają przy naciskaniu, jak w maszynach profesjonalnych (np. ZX SPECTRUM…)?” Naprzeciw tym, niewątpliwie słusznym, żądaniom wyszli twórcy układu 6502 i… odpowiednio go zaprojektowali. Dzięki temu każdy, kto jako tako potrafi posługiwać się asemblerem (tak, tak! To właśnie TY!), może dowolnie zmieniać procedury obsługi przerwań (tak mądrzy ludzie nazwali to, co komputer robi w przerwaniu).

Spróbujmy więc zrobić z przerwaniami coś pozytywnego. I to najlepiej tak, by można było przy okazji pisać jakiś program w BASIC-u. O, mam już pomysł. Niech w lewym górnym rogu pojawi się jakiś napis. I jeśli ten napis będzie się pojawiał co 1/50 sekundy, ciężko będzie go skasować. Ba! Nawet jeśli komuś się to uda, napis ten pojawi się znowu, za dokładnie 1/50 s! Dobre, co? Wpisz więc taki oto program:

(UWAGA! Jeśli używasz któregoś z modułów FINAL, to wyłącz go instrukcją KILL)

Uruchom program znajomą instrukcją SYS 10000. Jeśli coś nie działa, to na wszelki wypadek odłącz swój moduł (nawet, jeśli nie jest FINAL’em). Na ekranie pokazał się napis „ROBIN HOOD” (a wiecie dlaczego? Bo nie YAD!). Jakiekolwiek próby skasowania tego napisu skazane są na niepowodzenie. Uczynić to można jedynie kombinacją klawiszy RUN STOP/RESTORE, przyciskiem RESET, wyłącznikiem zasilania lub przez wyciągnięcie wtyczki z kontaktu.

Dlaczego jednak TO działa? Popatrzmy. Na początku pojawia się tajemniczy rozkaz SEI. Odłóżmy go na chwilkę na bok. Co robimy dalej? Bierzemy młodszy bajt adresu początku procedury, którą nazwałem „IRQ2”, a która zaczyna się od adresu $271d, i wstawiamy go do komórki $0314. Następnie starszy bajt tego adresu wstawiamy do komórki $0315. Komórki $0314/$0315 mają w systemie operacyjnym C-64 specyficzną rolę. Zawierają bowiem adres, do którego komputer ma skoczyć każdorazowo po przyjęciu przerwania typu IRQ. Adres ten przedstawiamy w postaci młodszego i starszego bajtu, tak jak na przykładzie. Czyli, jeśli praca procesora zostanie przerwana przerwaniem IRO, procesor skoczy do naszej procedury IR02, znajdującej się pod adresem $271d. Wszystko jasne?

Teraz wróćmy do tajemniczego rozkazu SEI („SEt Interrupt mask” – włącz maskę przerwań). Rozkaz ten powoduje, że komputer nie przyjmuje przerwań IRQ. Po prostu te przerwania nie są wykonywane. Powoduje to flaga I, nazywana też maską przerwań. Tak długo, jak maska ta jest włączona, żadne IRQ nie będą dla komputera na tyle ważne, by przerywać pracę nad programem, który aktualnie wykonuje. W naszym porównaniu pracy komputera do odrabiania lekcji, maskowanie przerwań da się przedstawić jako wyciągnięcie wtyczki telefonu z kontaktu. Od tej pory każdy może dzwonić do woli, my i tak będziemy robić swoje. Przerwania IRQ wyłączam nie bez powodu. Zaraz po wyłączeniu przerwań zmieniamy wektor IRQ, czyli adres, do którego procesor skacze po przyjęciu przerwania. Może się bowiem zdarzyć, że przerwanie zostanie zgłoszone PO wstawieniu nowej wartości do $0314 a PRZED wstawieniem naszej liczby do $0315. (A dlaczego nie? Przecież przerwania IRQ powtarzają się co 1/50 s. Prawdopodobieństwo nie jest więc duże, ale po co ryzykować.). I co się wtedy stanie? Zwykle wektor przerwań wskazuje na adres $ea31. My zaś zmieniamy tylko młodszy jego bajt, na $1d. Komputer skoczy więc do $eaid, czyli w jakieś maliny, w których znaleźć się nie powinien. Natknie się tam na jakieś głupie RTS i – jak to mówią starzy wyjadacze – się powiesi. Wyłączenie przerwań na ten krytyczny moment jest więc racjonalne i ze wszechmiar uzasadnione. Zwłaszcza, że i tak po ponownym, poprawnym ustawieniu wektora wydajemy rozkaz CLI („CLear Interrupt mask” – wyłącz maskę przerwań). CLI zezwala na ponowne uruchomienie IRQ.

Na tym kończymy program. Pozostaje tylko RTS, by powrócić do BASIC- a. W BASIC-u możesz swobodnie działać, pisać programy, nagrywać, ładować programy i robić, co tylko dusza zapragnie. „ROBIN HOOD” przetrwa wszystkie te próby i straszyć będzie nadal. Zaszkodzić mu może jedynie władowanie jakiegoś programu, który zajmie komórki o adresach 10000 – 10050. Tam bowiem siedzi nasza procedura. Wykonuje się ona co 1/50 s, więc władowanie w jej miejsce jakiegokolwiek innego programu spowoduje niechybne wpadnięcie komputera w maliny i powieszenie z rozpaczy.

Procedura IRQ2 to banalne wyświetlenie tekstu na ekranie. Nie wyma żadnego komentarza. Może tylko kończący ją rozkaz JMP $ea31, a nie jak zwykle, RTS albo BRK. Powoduje on skok do zwykłej procedury obsługi przerwań IRQ. Komputer robi tam swoje.

Nie każdy wie, że naciśnięcie klawisza RESTORE to także przerwanie. W odróżnieniu od IRQ, najczęściej nie jest wywoływane przez zegar, ani żadne inne czynniki wewnętrzne. Przerwanie to zgłaszasz Ty sam tłukąc w klawisz, najczęściej w połączeniu z klawiszem RUN/STOP. Powoduje to wystanie wprost do procesora impulsu wywołującego przerwanie NMI (Non-Maskable Interrupt – przerwanie niemaskowalne). Komputer zwykle sprawdza, czy naciśnięty został drugi klawisz z „fantastycznej dwójki” i jeśli tak, to wykonuje procedurę tzw. ciepłego startu: wszystkie kolory ustawia na standardowe, likwiduje duszki, wyłącza SID itd. Ma też to do siebie, że przerywa działanie programu BASIC-owego nie czyniąc w nim żadnych zmian. Dlatego dla piszących w BASIC-u jest zmorą. Dla nas, speców od asemblera, zmorą nie będzie, a czasem nawet może się przydać. Dla ułatwienia podpowiem, że wektor przerwania NMI to komórki $0318/$0319. One wskazują adres, pod który trzeba skoczyć po przyjęciu każdego przerwania NMI. Wpisz więc taki oto programik:

Programik ten jest, szczęśliwie, jeszcze krótszy od poprzedniego. Ko mentarz ograniczę do rzeczy niezbędnych. Nie wstawiłem rozkazów SEI i CLI, bo przerwania NMI są niemaskowalne – taka już ich natura. Jeśli zdążysz nacisnąć RESTORE po przestawieniu młodszego bajta, a przed przestawieniem starszego – twoja wina, twoja strata. Procedurka NMI2 jest króciutka – zmieniamy tylko kolor ramki we wszystkim znanej komórce $d020 (jeżeli nie wszystkim – odsyłam do mapy pamięci!). Zaraz potem występuje tajemniczy jeszcze rozkaz RTI. Aby rozwiać jego tajemniczość podaję, że oznacza on „ReTurn from Interrupt”, czyli „powróć z przerwania”. Jego wykonanie spowoduje powrót do wykonywania uczciwej pracy czyli tego, co siedzi w programie. Tu jeszcze jedna ważna wiadomość. Je żeli przerwiesz przerwanie IRQ, komputer nie straci głowy, lecz spokojnie wykona przerwanie NMI, powróci za pomocą RTI do przerwania IRQ, a z niego – do programu, który leży „na dnie”.

Na temat przerwań można by jeszcze pisać długo i szczęśliwie. Na początek jednak ta dawka teorii jest całkowicie wystarczająca. Już niedługo będzie interfejs do Pamiętnika Artylerzysty, czyli o przerwaniach rastra to i owo, lecz z nieco innej strony. Wasz Niezmordowany Płaskokrążek Informacjonośno-Stołokulotoczny BARTEK KACHNIARZ

Dziś poznaliśmy rozkazy:
SEI – włączenie maski przerwań, czyli zakaz wywoływania przerwań IRQ

CLI – wyłączenie maski przerwań, czyli unieważnienie zakazu wykonywania przerwań IRQ

RTI – powrót z przerwania do miejsca, w którym program został przerwany

Kurs Assemblera cz. 7

 

SKOKI – KOMPUTER ŻABKĄ

Jak zapewne spostrzegteś, komputer ma już prostą umiejętność wypisywania głupot (czasem i mądrości, ale dużo, dużo rzadziej) na swoim ekranie. Czyli odpowiednie procedury już „siedzą” gdzieś we wnętrzu naszej maszyny. Najlepszym miejscem jest ku temu pamięć ROM, która to potrafi przechowywać swą zawartość niezależnie od tego, czy jest zasilana prądem, czy też nie. Dodatkową jej zaletą jest fakt, że nie da się zmienić jej zawartości.

No, ale dość tych głupot, przejdźmy do konkretów. Żeby wydrukować na ekranie jakąś literę trzeba nam było najpierw wstawić ją do akumulatora rozkazem LDA a potem wyrzucić na ekran jakimś innym rozkazem (STA, ale to pomińmy). Dużym ułatwieniem naszej pracy byłby, po pobraniu numeru litery, skok do jakiejś procedury (ach, stodkie języki wyższego rzędu i ich procedury!) bez przejmowania się, cóż mądrego jest właściwie w tych procedurach, byle jeno robiły, co chcemy (czyli drukowały litery; jeszcze, ale już niedługo – nie tylko).

W ROM C-64 istnieje procedura, która nazywa się tajemniczo „CHROUT’. Skrzaty mówią, że przy układaniu jej nazwy chodzito głównie o to, by nie dało się jej wymówić. A oznacza ona CHaRacter OUTput, czyli – wypchnięcie znaku. Służyć może do różnych celów – do wydrukowania danego znaku na drukarce, zapisania na dysku, albo (i o to właśnie chodzi) – na ekranie. Wystarczy jedynie wstawić znak do akumulatora (LDA) i skoczyć…

Krótkie, proste, ale skuteczne. Mamy „A”. Jeśli pomyślisz chwilę, zaraz się domyślisz, jak tą metodą drukować dłuższe wyrazy, czy całe zdania. Przy czym ciekawe jest, że wcale nie interesuje nas, jak to się dzieje – litera po prostu się pokazuje i już. Łatwo zauważyć, że działamy tutaj metodą taką samą, jak np. w BASIC-u czy PASCAL-u – jeżeli pewna część programu musi być powtarzana więcej razy, prościej jest odwoływać się do niej kiedy trzeba, a nie wpisywać przy każdej konieczności. Dzięki temu możliwe staje się osławione programowanie strukturalne. Swoją drogą, jeżeli nie byłoby ono możliwe w asemblerze, jak można by wyobrazić sobie taką możliwość w jakimkolwiek innym języku? Asembler to podstawa. WSZYSTKO, co można zrobić np. w BASIC-u – zostato już wcześniej zrobione w asemblerze, tylko że nie przez nas, lecz przez autorów interpretera.

Rozkaz JSR może więc nam stużyć, jeżeli chcemy z jakiegokolwiek miejsca programu skoczyć do innego. Dodatkową zaletą tego rozkazu jest fakt, że w pewnym miejscu pamięci, zwanym STOSem przechowuje adres, do którego powinien wrócić po wykonaniu procedury. Powrót następuje automatycznie, po napotkaniu rozkazu RTS. Jeżeli procesor trafi na ten rozkaz, to powraca do głównego biegu programu.

Innym „skaczacym” rozkazem jest JMP (JuMP). Jego cechą jest to, że skacze bezpośrenio pod wskazany adres i nie zostawia żadnych znaków szczególnych (takich jak JSR na stosie), by można byto go po nich wyśledzić. Jeżeli skoczymy gdzieś poprzez JMP, nie istnieje możliwość powrotu do poprzedniej częsci programu. Rozkaz ten działa więc tak samo jak instrukcja GOTO w BASIC-u.

Jeszcze winien jestem wyjaśnienia w sprawie dalszego uproszczenia drukowania tekstów na ekranie. Otóż w pewnym miejscu w pamięci ROM istnieje procedura, która ma za zadanie wyświetlenie zadanego tekstu. Ot, i wszystko. Nie musimy już w tym celu konstruować pętli, pobierać kolejnych znaków itd. Wszystko zrobili już za nas autorzy systemu operacyjnego. Trzeba tylko:

a) wiedzieć gdzie jest ta procedura,
b) umieć zlecić komputerowi jej wykonanie, czego właśnie mam zamiar Was nauczyć.

Na początek muszę Wam przekazać, jak komputer zapisuje w pamięci dłuższe adresy. Robi to za pomocą tzw. starszych i młodszych bajtów. Aby wyliczyć jakiś adres mając starszy i mtodszy bajt, trzeba ten starszy przemnożyć przez 256 i do wyniku dodać młodszy. Brzmi to może nieco zawile, ale nie zapominajmy, że myślimy ciągle systemem dziesiętnym (ach, gdyby tak Bóg stworzył cztowieka z szesnastoma palcami!), komputer zaś dwójkowym, którego skróconym zapisem jest system szesnastkowy. Dlatego też roztożenie liczby dwubajtowej (czyli większej niż 255, $ff) jest w systemie szesnastkowym proste, w dziesiętnym zaś – już nie tak bardzo. Dla liczby $1234 starszy bajt (z angielska MSB – More Significant Byte) wynosi $12, zaś młodszy (LSB – Less Significant Byte) – $34. Liczbę dzielimy więc po prostu na dwie części. Pierwsze dwie cyfry to bajt starszy, dwie ostatnie – młodszy. Zaś w systemie dziesiętnym rozpiska liczby 12345 wygląda tak: 12345 dzielimy na 256 (tu ukłon w stronę red. Kalkulatora), wynik: 48.222656. Od tego odrzucamy część po przecinku, zostaje więc 48. I to jest starszy bajt. Przechodzimy więc do młodszego. Pomnóżmy 48 przez 256, wyjdzie 12288. Teraz musimy jeszcze od naszej początkowej liczby (12345) odjąć to nieszczęsne 12288. Mi wyszto 57. Ten etap byt zresztą najbardziej dramatyczny. Stary, blisko pięciocentymetrowej (!) grubości Texas Instruments odmówił współpracy i musiałem szukać czegoś innego. Sprawdźmy jednak, czy wynik jest prawdziwy. 48*256+57=12345. Wszystko się więc zgadza. Dla porządku przedstawię jeszcze algorytmy w BASIC-u:

MSB=I NT(AD/256)
LSB=AD-256*MSB

Na pewno przydadzą się nie raz. Do czego jednak byty potrzebne w tym przypadku? Do poinformowania komputera o adresie, od którego zaczyna się tekst do wyświetlenia na ekranie. Wpiszmy teraz jakiś tekst do pamięci, od adresu – niech będzie $3000, czyli 12288, posługując się metodą przedstawioną przeze mnie w poprzednim odcinku. Tym razem jednak koniecznie wstaw na końcu znaczek „@” o kodzie $00, 0. Gotowe? Jeśli tak, to wpisz:

…i uruchom to jak zwykle – SYS 10000, G2710, czy strzałka w lewo, 3 i S. Teraz na ekranie pojawił się cały potrzebny Ci tekst, bez bólu, szybko i skutecznie. Jeżeli masz ochotę tekst ten jeszcze jakoś sformatować, zmienić kolory, czy co tam jeszcze – skorzystaj z tabeli kodów ASCII znajdującej się gdzieś pod koniec instrukcji obsługi. Jak widać, rolę starszego i młodszego bajtu wzięty na siebie dwa rejestry procesora – jako starszy wystąpił rejestr Y, jako młodszy – akumulator. Jest więc, jak obiecałem – umiemy wydrukować tekst za pomocą jednego rozkazu.

A teraz miła niespodzianka dla użytkowników TurboAssemblera. Nie wiem, czy metoda ta zadziała w innych programach tego typu, ja sprawdziłem ją jeszcze w Magic Assemblerze. Wpisz (tym razem monitorowcy mogą np. zamknąć oczy):

Działa? I to jak! Od tej chwili możesz w ten sposób ułatwić sobie działania na młodszych i starszych bajtach tym właśnie prostym sposobem, o czym zawiadamia ostrzący przy szkwatach

Kurs Assemblera cz. 6

 

ROZKAZ CMP ET CONSORTES

Czy pamiętacie jeszcze, jak w upalne lipcowe dni wypisywaliśmy na ekranie jakieś dziwne stowa o maniakach i pętlach? Tak? To dobrze. Nie? To nieszczególnie dobrze – powtórne przejrzenie tego odcinka nie zaszkodzi. Co my tu mamy… CPX – instrukcja, która wtedy wtaśnie pierwszy raz się pojawiła. A służyła… do porównania zawartości rejestru X z podaną liczbą.

Tu wypada postawić pytanie: na jakiej zasadzie rozkaz ten działa? Komputer odejmuje liczbę podaną jako argument (albo też zawartość komórki podanej jako argument, ale to i tak na jedno wychodzi) od zawartości rejestru X. Tu trzeba jeszcze zauważyć, że odejmowanie to odbywa się bez przeniesienia, czyli stan flagi C przed wykonaniem tego rozkazu nie ma wpływu na wynik (czyli inaczej niż w SBC!). Sam wynik nie jest nigdzie przechowywany, ale – żeby całe nasze działanie nie poszło na marne – komputer odpowiednio do wyniku wygasza lub zapala znaczniki: Zera, Przeniesienia i Liczby ujemnej.

Ale jak w praktyce wygląda ta „odpowiedniość”? Najpierw zbadajmy zachowanie znacznika Zero. Wiemy, że jeśli znacznik ten jest zapalony, to oznacza to, że wynikiem ostatniej operacji matematycznej było zero. Jeżeli zaś wynik był różny od zera, to flaga C będzie zgaszona. Wpiszmy więc, tak dla próby, ten oto program:

Uruchamiamy nasz program i… co widzimy? W lewym górnym rogu ekranu pojawiło się zero. Dlaczego tak się stato? Zanalizujmy program. Najpierw do akumulatora wstawiamy liczbę $30, co odpowiada kodowi ekranowemu zera. Potem do rejestru X wstawiamy liczbę $17, zaraz zaś potem porównujemy zawartość X z liczbą $17 (przy czym sama liczba nie jest tu aż taka istotna; ważne, by była w obu rozkazach taka sama). Po operacji porównania następuje nowy rozkaz: BEQ. BEO $adres to rozkaz, po wydaniu którego komputer sprawdza stan flagi Zero. Jeśli flaga ta jest zgaszona, to ze spokojem przechodzi do następnego rozkazu. Jeżeli zaś znacznik jest zapalony, to program skacze do komórki o podanym adresie. BEO to skrót angielskich stów „Branch if EQual to zero”, czyli (ttumaczenie wolne) „skocz, jeśli wynik równy był zeru”. Oznacza to, że jeśli wynikiem operacji CPX #$17 było 0, to skoczyć trzeba do $271a (oznaczonego etykietą PISZ). Tam znajduje się, znany nam już, rozkaz STA powodujący wydrukowanie zera w lewym górnym rogu ekranu.

Dobrze. Wszystko działa więc zgodnie z przewidywaniami. Zróbmy teraz mały eksperyment: zmień liczbę przy rozkazie CPX na – dajmy na to – $47 albo $00 – byle nie było to $17. Uruchom teraz program. I co? Znowu jest zero? Nie! Tym razem w rogu ekranu pojawiła się jedynka. O czym to świadczy? Przejrzyjmy jeszcze raz program… Najwyraźniej rozkaz BEQ nie zostat wykonany, przez co do akumulatora wzięta została liczba $31 (oznaczająca jedynkę). Mamy więc pierwsze konstruktywne wnioski: jeżeli argument przy rozkazie CPX równy jest zawartości rejestru X, to zapala się wskaźnik Zero. Jeśli liczby te będą różne, wskaźnik ten gaśnie.

Sprawdźmy teraz, jak – w zależności od argumentów – zachowa się wskaźnik Przeniesienia, czyli C. Zmodyfikujmy nasz program tak, by wyglądał jak ten listing:

Przed próbą uruchomienia winien jestem jeszcze wyjaśnienia na temat nieznanego dotąd rozkazu: BCC. BCC to kolejny skok warunkowy. Jego wykonanie zależne jest od stanu znacznika C, czyli Przeniesienia. Jeśli C jest zgaszone, to komputer skacze do miejsca pamięci podanego jako argument rozkazu. Jeśli C jest zapalone, to rozkaz jest ignorowany. Skrót BCC (bo wszystkie mnemoniki asemblera procesora 6502 to TLS-y*, po prostu) oznacza „Branch if Carry Clear”, czyli „skocz, jeśli znacznik przeniesienia jest zgaszony”. Wszystko jasne?

To jadziem. Wynikiem działania programu jest tym razem jedynka. Oznacza to, że flaga C jest zapalona. Proponuję teraz kilka prób ze zmianą liczby przy rozkazie CPX. (Teraz Ty działasz!) No i co? Masz już gotowe wnioski? Jeśli tak, to porównajmy je z moimi.

Mi wyszło, że:
1) Jeśli zawartość rejestru jest większa niż liczba przy rozkazie porównania, to znacznik C będzie zapalony.
2) Jeśli zawartość rejestru jest równa liczbie przy rozkazie porównania, to znacznik C będzie zapalony.
3) Jeśli zawartość rejestru jest mniejsza niż liczba przy rozkazie porównania, to znacznik C będzie zgaszony. Teraz zmień w linii BCC $271 a (czy też BCC PISZ) mnemonik BCC na BCS. Rozkaz BCS oznacza „Branch if Carry Set”, czyli „skocz, jeśli znacznik przeniesienia jest zapalony”, zaś jego działanie jest dokładnie odwrotne niż BCC. Teraz jeśli uruchomimy program, wyniki będą dokładnie odwrotne niż poprzednio. Wiadomo dlaczego? Kolejnym znacznikiem, na który wpływać ma rozkaz CPX, jest znacznik Liczby ujemnej. Po zmianie rozkazu skoku program wyglądał będzie tak:

Nowy rozkaz BPL oznacza „Branch if PLus”, czyli „skocz, jeśli dodatnia”. Tym razem skok zależeć więc będzie od faktu, czy wynik ostatniej operacji będzie ujemny czy dodatni. W artytmetyce dwójkowej przyjętej przez projektantów układu 6502 (czyli tego procesora, który siedzi w Twoim C-64), liczby ujemne odróżnia się od dodatnich tym, że mają one zapalony siódmy, najstarszy bit. I to wtaśnie jest najśmieszniejsze. Jeżeli masz np. liczbę %10010011, to nigdy nie wiesz, czy dziesiętnie jest to (128+16+2+1) 147, czy też (-16-2-1) -19. Po prostu nie wiadomo. Dlatego też korzystanie z liczb ujemnych w asemblerze jest, delikatnie mówiąc, ograniczane w miarę możliwości. Zwtaszcza, że w bajcie, którego pierwszy bit jest stracony na znak liczby, mieści się dokładnie dwa razy mniej informacji niż w zwykłym. Pomimo tego wszystkiego ze wskaźnika Liczb ujemnych (Negative) przydaje się korzystać w innych sytuacjach. Przy współpracy z rozkazem CPX zastosowanie jego jest jednak dość ograniczone.

Teoretycznie wskaźnik N zostaje zapalony, gdy zawartość rejestru jest mniejsza od liczby, do której chcemy ją porównać. W rzeczywistości napotykamy jednak kilka problemów. Na przykład, gdybyśmy wzięli liczbę $ff (255) i porównali ją z liczbą $04 (4), to flaga N zostałaby zapalona. Czyli 4 ma być większe od 255? Ale numer! A to wynika po prostu z tego, że liczba 255 jest zbyt duża, by można do niej stosować zasady ujemności. Ujemne liczby muszą się zawierać między -127 a 127. Inaczej wychodzą brednie. Podobne brednie wychodzą i w innych przypadkach. Czyli. do wyników rozkazu BPL powinniśmy zachować dość daleko idącą rezerwę i starać się nie nadużywać tego skoku.

Rozkazem przeciwnym BPL jest BMI („Branch if Mlnus”, „skocz, jeśti ujemna”). Stosowanie jego napotyka podobne kłopoty jak BPL.

Cierpliwy i uważny Czytelnik zapewne zauważył już, że jakoś dziwnie tekst nie jest powiązany z tytułem. W nagłówku jest bowiem mowa o jakimś CMP, zaś my – jak do tej pory – zajmujemy się tylko tymi „consortes”. Czas wreszcie przedstawić gtównego bohatera: rozkaz CMP!

CMP oznacza „CoMPare accumulator” czyli „porównaj akumulator”. Działanie jego jest identyczne, dokładnie takie samo – prócz jednego szczególika – jak CPX. Szczególikiem tym jest fakt, że jako „mięso armatnie” do porównywania nie idzie zawartość rejestru X, a samego akumulatora. W świecie rozkazu CMP nie może – skoro jest rozkaz CPX odpowiedzialny za rejetr X – zabraknąć rozkazu porównania dla rejestru Y. Rozkazem tym jest CPY („ComPare Y”, „porównaj Y). Analogicznie, wszystkie flagi ustawiane są dokładnie tak samo, jak po rozkazach CMP i CPX.

 

‚ TLS to skrót od stów TrzyLiterowy Skrót. Zwrotu tego używamy, gdy chcemy zrobić na kimś dobre wrażenie lub zaciemnić nieco sytuację.

Dziś poznaliśmy rozkazy:
CMP – porównanie zawartości akumulatora z podaną liczbą

CPY – porównanie zawartości rejestru Y z podaną liczbą

BEQ – skok do podanego adresu, pod warunkiem, że wynikiem ostatniej operacji matematycznej było 0.

BCC – skok do podanego adresu, pod warunkiem, że w wyniku osstatniej operacji matematycznej znacznik przeniesienia został zgaszony.

BCS – skok do podanego adresu, pod warunkiem, że w wyniku ostatniej operacji matematycznej znacznik przeniesienia został zapalony.

BPL – skok do podanego adresu, pod warunkiem, że wynikiem ostatniej operacji matematycznej była liczba dodatnia.

BMI – skok do podanego adresu, pod warunkiem, że wynikiem ostatniej operacji matematycznej była liczba ujemna.

Kurs Assemblera cz. 5

Rolowanie bajtów i nie tylko

Zawartość komórek możemy nie tylko dodawać lub odejmować (co już od dobrego miesiąca umiemy). 6502 daje nam także bogate możliwości PRZESUWANIA bitów w obrębie jednego bajtu. Wiemy, że bajt składa się z ośmiu bitów, z których każdy może mieć wartość 0 lub 1. Jeżeli ułożymy je wszystkie w jednej linii, możemy je potraktować jako osiem dwójkowych rzędów wielkości. Weźmy jakąkolwiek liczbę. Niech to będzie dla przykładu 105. Po przełożeniu tego na układ dwójkowy dostaniemy liczbę 01101001. Jeśli przesuniemy to co mamy o jeden bit w lewo (po prawej stronie wstawiając 0), dostaniemy liczbę 11010010. Krótkie obliczenia (128+64+16+2)… wynik: 210! Czyli dokładnie dwa razy więcej! Oznacza to, że przesunięcie bitów w lewo jest równoznaczne z pomnożeniem danej liczby przez dwa. Czy można wyciągnąć z tego wniosek, że przesunięcie bitów w prawo to nic innego jak podzielenie liczby przez dwa? Właściwie tak. Każdy bit przekaże swoją zawartość sąsiadowi po prawej. A wiemy, że sąsiad po prawej ma dwa razy mniejszą wartość liczbową. Dlatego też możemy podejrzewać, że zaiste tak będzie. Weźmy więc znowu 105 (czyli %01101001, procentem programiści zwykli poprzedzać liczby zapisane dwójkowo, podobnie jak dolarem – szesnastkoso) i przesuńmy w prawo (po lewej wstawiając 0). Mamy więc %00110100, czyli (32+16+4) 52. A przecież 105 na 2 to 52,5! Co stało się z brakującymi 0,5? Przyjrzyjmy się naszym liczbom dwójkowym. W pierwszej mamy cztery jedynki, w drugiej zaś już tylko 3. Wniosek z tego taki, że nie możemy ot tak, po prostu wyrzucać na śmietnik niepamięci bitów, które wypadły nam poza bajt podczas przesuwania. Trzeba je gdzieś wstawiać. Potrzebny jest nam choć jeden bit, do którego łatwo będzie się dostać. Jak znalazł jest tutaj flaga przeniesienia (bit C, czyli Carry). Przeniesienie przecież po to właśnie istnieje, by wstawiać doń bity, które „wyskoczyły” nam podczas jakichś operacji arytmetycznych poza przewidziane osiem bitów. Tak właśnie działają rozkazy ASL i LSR. ASL to przesunięcie zawartości bajtu w lewo (Arithmetic Shift Left). Jeżeli wykonamy je na jakiejś komórce, bity 0 – 6 przesuną się o jedno oczko w lewo. Jako nowy bit 0 wstawione będzie 0. Jeśli stary bit 7 był jedynką, flaga C zostanie zapalona, jeśli zaś był zerem-zgaszona. Mam nadzieję, że klarownie objaśnia to rys. 1. Sprawdźmy, jak rozkaz ASL dziata w praktyce. vlusimy napisać program, który wstawi do pamięci akąś wartość, następnie przesunie ją w lewo i da tam jakoś znać, jeśli znacznik C został zapalony.


*=$2710
LDA #$40
STA $2800
ASL $2800
LDA #$00
BCC ZERO
LDA #$01
ZERO STA $2801
BRK

Uruchom program przez SYS 10000 albo G2710. W komórce $2800 (dziesiętnie 10240) znajdzie się wynik przesunięcia, w $2801 (10241) zaś to, co znalazło się w znaczniku przeniesienia. Jeśli wpiszemy program tak, jak jest podany-w $2800 dostaniemy $80, a w $2801 – $00, czyli tak, jak to było do przewidzenia. Wpiszmy jednak w pierwszej linii LDA #$ff, a potem uruchomimy program. $ff to % 11111111. Po przesunięciu w lewo dostaniemy wynik $fe, czyli %11111110. W komórce $2801 (czyli 10241) będzie jedynka, która bierze się z ostatniej jedynki bajtu poprzedniego. Powinna być więc traktowana jako dodatkowy bit przesuwanego przez nas bajtu. Proponuję trochę się pobawić tym programikiem-pomoże to dokładnie pojąć działanie rozkazu ASL. Instrukcją odwrotną jest LSR. LSR to przesunięcie w prawo (Logical Shift Right). Ten rozkaz przesuwa bity w bajcie w prawo, przy czym skrajny prawy bit jest wstawiany do C, zaś za skrajny lewy komputer bierze 0. Działanie tego rozkazu objaśnia rys. 2 oraz ten oto niewielki programik:


*=2710
LDA #$40
STA $2800
LSR $2800
LDA #$00
BCC ZERO
LDA #$01
ZERO STA $2801
BRK

Po uruchomieniu programu -zgodnie z przewidywaniami – w komórce $2800 znajdzie się $20 (32), czyli dwa razy mniej niż $40 (64). Wszystko się więc zgadza. Para opisywanych rozkazów okazała się jednak niewystarczającą. Nie dla wszystkich bowiem zastosowań za nowy, skrajny bit można było przyjąć 0. Nie byłoby też szczególnie wygodne, gdyby zaprojektować dwa rozkazy, które wstawiałyby 1 zamiast 0. Dla większej elastyczności i wygody powstała więc dwójka ROL i ROR. ROL to ROtate Left, czyli obrócenie w lewo. Od rozkazu ASL różni się tym, że jako skrajny prawy bit wstawiane jest nie – 0, lecz to, co było w C PRZED wykonaniem rozkazu. Zaś to, co było na lewym skraju, wchodzi do C (tak jak w ASL). Ilustruje to rysunek 3 oraz ten oto program:


*=$2710
SEC
LDA #$40
STA $2800
ROL $2800
LDA #$00
BCC ZERO
LDA #$01
ZERO STA $2801
BRK

W komórce $2800 znajdzie się $81, czyli %10000001. $40 to 101000000. Jedynka, która została przeniesiona z szóstego do siódmego bitu a to, co było w przeniesieniu, pojawiło się po prawej. Jeżeli napiszemy CLC zamiast SEC, wynik będziemy mieli identyczny, jak w przypadku ASL -jako bit zerowy będzie 0. Rozkazem, który działa w stronę przeciwną jest ROR. ROR to skrót od angielskich słów ROtate Right, czyli obróć w prawo. Jak wskazuje nazwa, przesuwa on zawartość bajtu w prawo. Poza tym w miejsce bitu siódmego wstawia zawartość Carry, a to, co było w bicie zerowym, do przeniesienia odsyła. I bardzo dobrze (rys. 4).


*=$2710
SEC
LDA #$40
STA $2800
ROR $2800
LDA #$00
BCC ZERO
LDA #$01
ZERO STA $2801
BRK

Po wykonaniu w $2800 dostaniemy z $40 (czyli %00010000) liczbę $a0 (%10001000). W $2801, zgodnie z przewidywaniami będzie 0 – bit zerowy był zgaszony.

Oczywiście, rozkazy te służą nam nie tylko do przesuwania jakichś abstrakcyjnych bajtów w prawo czy w lewo. Mogą się nam przydać, jeśli np. będziemy chcieli sprawdzić po kolei bity danego bajtu, jeżeli będziemy chcieli zrobić procedurę płynnego przesuwania napisów na ekranie graficzny albo mnożyć/dzielić coś przez wielokrotności 2.

 


Rys. 1
ASL
C <- 7 6 5 4 3 2 1 0 <- 0

Rys. 2
LSR
0 -> 7 6 5 4 3 2 1 0 -> C

Rys. 3
ROL
->---------->---------->-
- 7 6 5 4 3 2 1 0 <- C <-

Rys. 4
ROR
-<----------<----------<-
-> C -> 7 6 5 4 3 2 1 0 -