energy.scene.pl

Popularny magazyn na ośmiobitowe Atari

Wybierz muzykę:

Energy 1: 1 2 3 4 5
Energy 2: 1 2 3 4 5 6 7 8 9 10 11 12
Print this page
Lekcja asemblera cz. 1

Jaskier
autor: jaskier

Temat nauki programowania w języku niskiego poziomu (assemblerze) był chyba już podejmowany we wszystkich magazynach i czasopismach o małym Atari. Sam kiedyś byłem początkującym i doskonale pamiętam jak wiele mi one pomogły nie tylko w nauce programowania, ale również w zrozumieniu sposobu działania komputera. Jako szczególnie dobre uważałem artykuły w czasopiśmie Tajemnice Atari. Były one z całą pewnością najlepiej zredagowane, a przede wszystkim napisane przez ludzi, którzy faktycznie znają się na programowaniu. Niestety cykl ten w końcu się skończył. Patrząc na to z obecnej perspektywy można stwierdzić, że róznica pomiędzy kimś, który przebrnął przez cały ten cykl, a średnim nawet koderem ze sceny jest kolosalna. Sam przechodziłem tę drogę, więc wiem jakie to trudne. Nie pomogły mi tutaj inne artykuły w innych czasopismach i magazynach, gdyż w nich poziom był wręcz zupełnie zerowy.

 

Na szczęście Energy jest zupełnie innym magazynem. Zawsze aspirowaliśmy do tego, aby być najlepszym magazynem na scenie, dlatego też nie mamy zamiaru kontynuować obłędnej drogi naszych poprzedników. Chcemy w końcu nauczyć czegoś porządnego początkujących programistów, a nie tylko poziomego scrollu. Jako punkt wyjścia przyjmiemy poziom jaki osiąga się po przejściu kursu z Tajemnic Atari. Uznaliśmy to za dobrą granicę, gdyż mają one najwyższy poziom, a nie chcielibyśmy powtarzać po raz któryś tego samego. Ponadto były one sprzedawane w dość dużej ilości egzemplarzy, przez co chyba są one dostępne dla każdego. Jeżeli nie masz wszystkich numerów, to poszukaj, popytaj wśród kolegów. Na pewno ktoś będzie miał brakujące egzemplarze i Ci pożyczy.

 

Jeżeli nie opanowałeś jeszcze biegle tego co jest napisane w Tajemnicach, to wcale nie znaczy, że nasze artykuły będą dla Ciebie nieprzydatne. Na pewno znajdziesz coś co Ci się przyda. Jeśli zaś już uważasz się za nieco wprowadzonego w temat, to zapraszamy do lektury. W każdym naszym wydaniu znajdzie się po kilka artykułów dla programistów. Nawet w już wydanym pierwszym numerze znajdują się takie trzy: GED od środka, Playery i Packowanie danych. Najpierw jednak polecamy wszystkim przeczytanie tego artykułu. Ludzie są omylni i nawet w Tajemnicach Atari zdarzyło się kilka błędów i niedomówień. Postaramy się tutaj je naprawić. Dlatego "Lekcja assemblera" nosi numer zerowy. Nie ma w niej bowiem nowych treści, a jedynie drobne poprawki do już przedstawionych rzeczy.


Na początek krótka dygresja i przypomnienie starego dowcipu:


Po kolejnych "wolnych" wyborach za czasów PRL-u spotyka się dwóch przyjaciół.

- Uwierzysz - mówi jeden. - Aż 98 procent poparcia dla aktualnych władz.

- Ja to zawsze mam pecha - odpowiada drugi. - Ciągle spotykam tylko te 2 procent niezadowolonych.


Co to ma wspólnego z Atari? Otóż we wszyskich publikacjach o nim pisze się, że prawie wszystkie komputery są wyposażone w nowszy procesor 65C02, a jedynie pierwsze kilkaset egzemplarzy sprzedanych w Pewex-ie było wyposażonych w procesor 6502. Ja to zawsze mam pecha. Ciągle spotykam tylko komputery z procesorem 6502.

 

Pora zająć się sprawami poważniejszymi. Na początek zajmijmy się samym stylem pisania programów. W Tajemnicach uczono dość ciekawego stylu pisania: z wyróżnionymi etykietami, z wartościami stałymi, z komentarzami i oczywiście z nazwami wszystkich używanych komórek systemowych. Podobno wszystko to po to, aby program był bardziej czytelny.

 

No cóż. Mam już trochę doświadczenia, na podstawie którego mogę powiedzieć, że nawet przy najbardziej wymyślnych komentarzach już po tygodniu nie wiemy co i gdzie program robi. A zatem po co je stosować? Tylko zawalają cenne miejsce w edytorze, przez co plik, który piszemy nie będzie mógł być taki długi i będziemy musieli uciekać się do kłopotliwego ICL-owania.

 

Co do robienia dużych wcięc (przynajmniej 5 spacji na początku każdej linii), tak aby linie zaczynające się od etykiety były bardziej widoczne, to muszę tutaj stwierdzić, że tekst assemblerowy jest i tak zupełnie nieczytelny, więc próbowanie go uczytelnić jest i tak skazane na niepowodzenie. Więc po co to robić? Zajmuje to tylko niepotrzebnie 4 bajty w każdej linii. A zatem je skasujmy. W każdej linii wystarczy tylko jedna spacja przed kodem rozkazu.

 

Co do używania wartości stałych. To czym według ciebie różnią się następujące programy:

CHN1 EQU $10
IO_COM EQU $342  IO_COM EQU $342
CIOV EQU $E456   CIOV EQU $E456
 LDX #CHN1        LDX #16
 LDA #3           LDA #3
 STA IO_COM       STA IO_COM
 JSR CIOV         JSR CIOV

Odpowiedź jest prosta. Ten pierwszy zajmuje więcej pamięci w edytorze. A czy ten drugi jest mniej czytelny? Nie sądzę. Każdy ciołek chyba wie, że ładując wartość 16 do rejestru X, odwołujemy się do pierwszego kanału.

 

Ostatnia sprawa. Nazywanie komórek systemowych. Podobno to też ułatwia rozumienie programu. Ale wyobraź sobie sytuację, kiedy ty, dla oznaczania komórki sterującej pracą układu GTIA używasz etykiety "gtictl", a nagle dostałeś od kolegi do przejrzenia program, w którym ta sama komórka ma nazwę "gticts". Czy taki program faktycznie będzie bardziej zrozumiały? Nie sądzę. Byłby już o wiele bardziej czytelny, gdybyście obydwaj zamiast różnych dziwnych etykiet stosowali po prostu adresy komórek.

 

Zaraz, zaraz... A dlaczego faktycznie tak nie zrobić? Oszczędza się wtedy znowu miejsce w edytorze, bo nie trzeba stosować setek EQU przed każdym programem, a ponadto przy każdej instrukcji wiemy o co chodzi. Może ktoś powiedzieć, że etykieta jest bardziej zrozumiała, gdyż bardziej pamiętamy słowa niż liczby. Bardzo przepraszam, ale jeżeli ktoś nie pamięta co robi skok pod $e456, to moim zdaniem nie będzie również pamiętał co robi skok pod etykietę CIOV.


Zanim zajmę się kolejnym wyliczniem błedów w Tajemnicach,chciałbym przedstawić jeszcze jeden problem, który w literaturze fachowej urósł wręcz do rangi zabobonu. Tym zabobonem jest przerwanie NMI. Co do magicznych rzeczy, które są w tym przerwaniu robione urosły już takie legendy, że nikt nie ma odwagi tego przerwania wyłączyć czy zastąpić go w całości własnym, a jedynie co bardziej odważni trzymają trochę danych pod ROM-em i w chwili potrzeby szybciutko go wyłączają, przepisują co trzeba i z powrotem włączają tak, aby w żaden sposób nie zakłócić przerwań.

 

Co według niektórych autorów artykułów miałoby robić to przerwanie? Przede wszystkim odnawiać wartości niektórych komórek związanych z różnymi rejestrami układów (adresy od $d000 do $d7ff). Według nich komórki te szybko "zapominają" swoje wartości i trzeba je co 1/50s. odnawiać. Dlatego właśnie mają one swoje kopie w pamięci RAM. Ponadto przerwania NMI są konieczne podczas operacji wejścia-wyjścia. Również wieloma legendami obrósł temat maksymalnego czasu wykonywania wszystkich 3 typów przerwań NMI. Wygląda to wszystko bardzo skomplikowanie.

 

Spróbuję to uprościć:

 

Jak sama nazwa wskazuje, przerwania NMI procesor nie może zignorować. Kiedy tylko dostanie on sygnał wykonania tego przerwania, to natychmiast, jak tylko skończy wykonywanie aktualnego rozkazu, zapisuje na stosie stan rejestru PC oraz P, a następnie do rejestru PC wstawia adres pobrany z komórek $fffa i $fffb. Od tego adresu zacznie wykonywać się program przerwania. Jeżeli włączony był ROM, to będzie to systemowa procedura przerwania, a jeśli nie, to będzie to nasza własna procedura. Pierwsze co robi systemowa procedura, to sprawdza komórkę $d40f. Jeżeli zawiera wartość ujemną (BMI), to znaczy, że należy wykonać przerwanie DLI. W przeciwnym razie wykonuje przerwanie VBLANK.

Drobna uwaga. Co prawda przerwania NMI procesor nie może zignorować, to jednak, ponieważ jedynym źródłem przerwań NMI jest w komputerach Atari procesor graficzny ANTIC, to można w celu zablokowania przerwań NMI po prostu zablokować ich generowanie przez ANTIC. Sterują tym 2 najwyższe bity komórki $d40e. Wyzerowanie najwyższego blokuje przerwania DLI, a niższego VBLANK.

Przerwanie VBLANK dzieli się na 2 części. Natychmiastową, wykonywaną zawsze i opóźnioną, wykonywaną tylko wtedy, jeżeli komórka CRITIC=$42 jest równa zero, oraz przed wywołaniem przerwania w rejestrze P był skasowany bit I. Oznacza to np: że podczas ładowania wykonuje się tylko część natychmiastowa przerwania. Ciekawe jest zatem skąd I. Chadwick w swojej książce "Mapping The Atari" wziął ograniczenie czasowe 3800 cykli dla tej fazy oraz 20000 dla części opóźnionej. Podczas operacji wejścia-wyjścia przerwania IRQ występują co określoną ilość cykli zależną od czybkości przesyłu danych. Aby przerwanie VBLANK nie powodowało zaburzeń musi trwać krócej niż owa przerwa. W przypadku standardowej prędkości dla stacji dysków jest to ok. 750 cykli, a przy prędkości transmisji 70000 bodów jest to nieco ponad 200 cykli. Ponieważ już systemowa procedura zajmuje prawie 200 cykli, więc nam z całą pewnością nie zostaje ich 3800. Co do fazy opóźnionej przerwania, to może ona sobie trwać tak długo jak tylko chce, byleby tylko nie następowała sobie "na pięty". Ma więc do dyspozycji wszystkie cykle, jakimi dysponuje procesor w czasie 1/50s. minus cykle wykorzystywane w innych przerwaniach. Niestety nie sposób jednoznacznie określić ile to jest. Zależnie od tego jaki mamy włączony ekran, może to być poniżej 20000 do nawet powyżej 30000. Co do ograniczeń czasowych przerwania DLI, to jeżeli działa ono podczas ładowania, to patrz: faza natychmiastowam, a jeżeli nie, to patrz: faza opóźniona.

 

Co takiego niezbędnego robi przerwanie VBLANK? Właściwie nic. To, że musi odnawiać wartości wpisywane do poszczególnych układów, to bujda. Zróbcie następujący eksperyment: zablokujcie przerwanie NMI i pozostawcie komputer włączony przez 10 dni. Jeżeli on to wytrzyma, to idę o zakład, że kolor obrazu nie zmnieni się ani trochę. Po co zatem jest to przepisywanie wartości? Po prostu większość z rejestrów układów jest tylko do zapisu. Dlatego też nie można z ich zawartości dowiedzieć się o aktualnym stanie systemu. Po to są więc właśnie komórki w RAM. Ponieważ ich zawartości są przepisywane do rejestrów układów, więc odczytując je możemy wiedzieć, jak są ustawione rejestry układów.

 

Jak zatem widać przerwanie VBLANK nie robi niczego tak naprawdę potrzebnego. Myślę więc, że nikt już nie będzie miał oporów przed pisaniem programów z wyłączonym ROM-em, a włączającym go tylko podczas operacji wejścia-wyjścia. Tak nawiasem mówiąc, to nawet podczas operacji wejścia-wyjścia nie jest potrzebna standardowa procedura VBLANK, czego dowodem jest pierwszy numer magazynu ENERGY, który już po wczytaniu pierwszych 3 sektorów obywa się bez ROM-u.


Pora teraz w końcu zająć się niedomówieniami w Tajemnicach Atari.


Numer 4/91: Rozkazów AND, ORA, EOR można faktycznie uczyć się z przedstawionej tam tabelki, jednakże znacznie prościej traktować jedną z wartości jako sito, a drugą jako np. mąkę, czy ziarno, które jest przez to sito przesiewane. To, jak i którą wartość tak potraktujemy nie jest ważne, gdyż operacje logiczne są symetryczne.

 

AND: jedna z wartości jest sitem, w którym bity o wartości 1 są dziurami, a druga wartość jest ową mąką, gdzie bity o wartości 1 są tymi przesiewanymi ziarenkami. Jak łatwo się domyślić, jeżeli ziarenko napotka w sicie wartość 0, to zostanie przez sito powstrzymane, a jeżeli natrafi na dziurę (wartość 1), to przeleci przez nią. Wynik interpretujemy podobnie jak drugą wartość. Spójrz na przykład:

           *   *
     % 1 0 1 0 1 0 1 0
 AND % 0 0 1 1 1 1 0 0
 =   % 0 0 1 0 1 0 0 0

Górna wartość to mąką, a dolna to sito. Ziarenka oznaczone gwiazdką natrafiły w sicie na dziury i przedostały się na drugą stronę. Możesz też tutaj naocznie się przekonać, że operacja AND jest symetryczna. Gdybyś pierwszą wartość zinterpretował jako sito, a drugą jako mąkę, to otrzymałbyś dokładnie ten sam wynik. Sprawdź.

 

ORA: podobnie jak w przypadku AND, z tym, że wartości 0 w sicie to dziury, a wartości 0 w mące, to ziarenka. Oto przykład:

         *           *
     % 1 0 1 0 1 0 1 0
 ORA % 0 0 1 1 1 1 0 0
 =   % 1 0 1 1 1 1 1 0

Równie dobrze obie wartości możemy traktować jako przezroczyste kalki z naniesionymi rysunkami (jedynki). Operacja ORA nakłada te kalki na siebie tak, że oba rysunki są widoczne.

 

EOR: ta operacja działa nieco inaczej. Nazywam ją czasami cudowną operacją przemiany. Jeżeli grałeś kiedyś w Boulder Dasha, to zrozumiesz o co chodzi. Otóż był tam taki specjalny murek, który przemieniał wrzucane w niego kamienie w diamenciki i odwrotnie. Podobnie dzieje się z operacją EOR. Jedynki w pierwszej wartości oznaczają właśnie taki murek, a zera zwykłą przestrzeń. Natomiast jedynki w drugiej wartości oznaczają diamenciki, a zera kamienie. Zarówno kamienie, jak i diamenciki przechodząc przez zwykłą przestrzeń nie ulegają zmianie. Natomiast przechodząc przez murek odpowiednio się zmieniają. Oto przykład:

     % 1 0 1 0 1 0 1 0
 EOR % 0 0 1 1 1 1 0 0
 =   % 1 0 0 1 0 1 1 0

Myślę, że teraz wszystko to stało się bardziej proste.


Numer 8/91: Dla tych, którzy nie zrozumieli zasady działania relokatora, podaję tutaj wyjaśnienie. Aby program po przepisaniu go pod inny adres działał prawidłowo, musimy zmienić wszystkie rozkazy, których adresy odwołują się do wnętrza programu. Podejrzane są o to wszystkie rozkazy skoków, oraz rozkazy operujące na pamięci, ogólnie wszystkie rozkazy o długości trzech bajtów. Rozkazy jednobajtowe i dwubajtowe nie są podejrzane, gdyż ich działanie nie zależy nigdy od umieszczenia w pamięci. Natomiast w rozkazach trzybajtowych należy sprawdzić, czy faktycznie są one zależne od umieszczenia programu, czy też odwołują się do adresów spoza programu. Ponieważ relekowany jest tylko sam program, a nie cała pamięć, więc jeżeli program odwołuje się do adresu spoza swojego obszaru, to ponieważ pamięć pod tym adresem nie jest przemieszczana, to nie należy adresu w rozkazie zmieniać. Owo sprawdzanie dokonywane jest w programie od etykiety TPE3_. Problemem jest zatem tylko sprawdzenie, które rozkazy są trzybajtowe. Program jest tutaj bardzo sprytny, gdyż nie sprawdza po kolei każdej możliwej instukcji, ale na podstawie kodu rozkazu dedukuje, ile on zajmuje bajtów. Dedukcja owa zaczyna się od rozkazu TAX znajdującego się 2 linie nad komentarzem: "* instr type check". Dedukcja owa jest bardzo prosta i opiera się na regularności w budowie kodu rozkazu. Sprawdzane są mianowicie 4 najniższe bity. Wartość 8 lub 10 oznacza, że rozkaz jest jednobajtowy. Wartości poniżej 8 oznaczają rozkazy dwubajtowe, natomiast wartości powyżej 10 oznaczają owe podejrzane rozkazy trzybajtowe. Największy problem jest z wartością 9. Wówczas sprawdza się jeszcze piąty bit. Jeżeli jest skasowany, to jest to rozkaz jednobajtowy, a jeżeli ustawiony, to jest to rozkaz trzybajtowy. Od tego wszystkiego są tylko 4 wyjątki. Rozkazy o kodach $00 (BRK), $40 (RTI), $60 (RTS) są jednobajtowe, a rozkaz o kodzie $20 (JSR) jest trzybajtowy.