Programowanie 6502 (1-3)


      Długo się zastanawiałem nad podjęciem decyzji o poprowadzeniu podobnej publikacji. Dlaczego? Sprawa jest prosta nie znam magazynu dyskowego, który doczekałby się kilkunastu wydań. Omówienie zagadnień związanych z programowaniem procesora 6502 jest bardzo obszerne. Jednak postaram się doprowadzić do publikacji w maximum pięciu odcinkach. Trzeba jednak liczyć się z tym, że każdy z nich będzie naprawdę kobylasty, bo będzie zawierał około 30 kB tekstu. Dodatkowym argumentem do przeprowadzenia tej publikacji był fakt wyposażenia magazynu SERIOUS w narzędzie do konwertowania artykułów na format Panther'a co umożliwi wszystkim czytelnikom łatwe uzyskanie demonstrowanych tu tekstów źródłowych programów. Dodatkowym argumentem do popełnienia tego czynu jest dla mnie "ucieczka" wielu starych scenowiczów, co zapewne spowoduje napływ nowych ludzi na scenę i co za tym idzie naturalne zapotrzebowanie na naukę assemblera.

      Kurs ten został już raz poprowadzony na łamach czasopisma "Tajemnice ATARI" a autorem jego jest jeden z najsłynniejszych programistów ATARI Janusz Bohdan Wiśniewski. Ostatnia uwaga, gdyby z jakichkolwiek przyczyn dalszy ciąg kursu nie mógł ukazywać się na łamach maga SERIOUS to doprowadzę do pojawienia się całości tego kursu na scenie choćby pod postacią plików tekstowych. Assemblerem którego będziemy używać, będzie najpopularniejszy chyba na naszej scenie Quick Assembler.

Zbycho Jabol/IDP

NIECO O PRZESYŁANIU DANYCH

      LDA jest bez wątpienia jednym z najczęściej używanych rozkazów procesora 6502. Przenosi on pojedyńczy bajt danych z pamięci do rejestru procesora zwanego akumulatorem. Argumentem rozkazu jest wskazówka, gdzie należy szukać tego bajtu. Na przykład rozkaz:

      LDA 712

spowoduje umieszczenie w akumulatorze liczby znalezionej w komórce o adresie 712. Jest to kolor obrzeża ekranu. Może zresztą słowo przenosi nie jest tu odpowiednie, ponieważ zawartość komórki 712 nie ulega zmianie. Jest to zatem bardziej kopiowanie, powielanie wartości. Podobnie odwrotny rozkaz:

      STA 710

przesyła zawartość akumulatora do komórki 710, lecz akumulator nie zmienia swojego stanu. Komórka ta określa kolor tła tekstu, a zatem po wykonaniu opisanej pary rozkazów tło będzie miało taki sam kolor jak dotychczas obrzeże. Przeważnie to jest czerń. Otrzymamy więc jasne litery na całkiem czarnym ekranie. Zwróć uwagę, że dzięki takiemu przeniesieniu koloru nie musimy nawet wiedzieć, jaka to właściwie jest liczba. Spróbuj to wykonać używając dostępnego sobie assemblera. Program może wyglądać tak:

      opt 21

      org 1152

      lda 712
      sta 710
      rts

      end

Rozkaz END stanowi informację dla asemblera, że ma zakończyć tłumaczenie programu. Znaczenie rozkazów OPT, ORG, RTS zostanie wyjaśnione niebawem. Chwilowo przyjmij że muszą one w podanej kolejności i formie oskrzydlać każdy z przykładowych programów. Przepisz przykład starannie, bacząc, by nie pominąć potrzebnych spacji, ani nie dopisać zbędnych znaków, przetłumacz go i wykonaj zgodnie z wymogami posiadanego asemblera. Wszystkie użyte tu przykłady są napisane i przetestowane pod kontrolą systemu Quick Assembler (nazywanego dalej QA) i będą działać również z JBW Asemblerem bez żadnych zmian. Są to zintegrowane pakiety edytora, asemblera i mini debugera umożliwiające bardzo szybkie przechodzenie od redagowania do testowania programu i z powrotem. QA udostępnia dla uruchamianego programu osobny ekran do którego można zaglądać w każdej chwili także w trybie edycji. Podziwjając efekty działania programu zastanów się, jak teraz przywrócić ekranowi jego poprzednią barwę( nie myślimy tu o tak "fizycznych" metodach jak klawisz RESET).Nie wiesz? Ja też. Po wykonaniu rozkazu STA poprzednia zawartość komórki pamięci niestety bezpowrotnie znika. Można czasem temu zaradzić jeśli się wie, co tam kiedyś było. Spróbujmy napisać tak:

      lda #148
      sta 710

Znaczek "#" z przodu argumentu wskazuje, że chodzi w tym przypadku o liczbę 148, nie zaś o komórkę o tym adresie. Tak więc liczba wędruje do akumulatora nie z konkretnego miejsca pamięci, lecz z wnętrza samego rozkazu. Tak wyczarowana wartość ustala nowy kolor tła tekstu. Przepisz i wykonaj ten przykład pamiętając o uzupełnieniu go dodatkowymi rozkazami jak w poprzednim programie.

      Jeśli jednak program ma być uniwersalny, odradzam stosowania stałych liczbowych. W kolejnym przykładzie dla zamiany kolorów liter i tła posłużymy się dodatkowym rejestrem procesora, zwanym X, rozkaz TAX przesyła liczbę z akumulatora do tego właśnie rejestru, zaś rozkaz TXA z X do A (akumulatora).

      lda 709
      tax
      lda 710
      sta 709
      txa
      sta 710

Komórka 709 określa właśnie kolor liter. Uruchom ten przykład tak jak poprzednie. Kolory uległy zamianie. Wykonaj ten program jeszcze raz, a kolory wrócą na swoje miejsca. To ważna cecha solidnego programowania: niczego nie psuć nieodwracalnie.

      Trzeci rejestr ogólnego stosowania nazywa się Y. Grupa rozkazów : TAX, TXA, TAY, TYA przesyła dane pomiędzy rejestrami wewnątrz procesora. Ich działanie nie wymaga chyba komentarza. Do komunikacji rejestrow X i Y z pamięcią służą, podobnie jak w przypadku akumulatora pary rozkazów LDX, LDY, oraz STX i STY. Wiedząc to możemy łatwo zaprogramować rotację (cykliczną zmianę) kolorów obrzeża tła i napisów:

      lda 712
      ldx 710
      ldy 709
      sta 710
      stx 709
      sty 712

Wykonaj ten program taką liczbę razy, aby kolory po zmianach wróciły do pierwotnego stanu. Patrząc jednak na tekst ostatniego przykładu nie łatwo jest się domyśleć o co tu chodzi. Jakikolwiek dłuższy program napisany w tym stylu byłby całkowicie nieczytelny. Na szczęście nie programujemy w języku maszynowym, który składa się wyłącznie z ciągów liczb i jest zupełnie niezrozumiały dla człowieka. Język asemblera zwany też językiem symbolicznym umożliwia nadawanie liczbom nazw, które następnie mogą być zamiast tych liczb używane podobnie jak kody rozkazów maszynowych zastąpione są trzyliterowymi kryptonimami. Mając na względzie czytelność programu łatwość jego analizy lub modyfikacji i zmniejszenie prawdopodobieństwa popełnienia błędu przepiszmy powyższy przykład tak:

ramka  equ 712
pole   equ 710
litery equ 709

       lda ramka
       ldx pole
       ldy litery
       sta pole
       stx litery
       sty ramka

Rozkaz EQU służy właśnie do utożsamiania wymyślonego przez nas symbolu takiego jak POLE z pewną liczbą. Asembler będzie traktował wszystkie dalsze wystąpienia tego symbolu (zwanego etykietą) tak jakby był tą właśnie liczbą. Etykieta stoi zawsze na początku wiersza, który ją definiuje. Dlatego asembler wymaga by w innych wierszach pierwsza kolumna pozostawała wolna. W praktyce zostawia się z przodu więcej spacji tak by symbole rozkazów zaczynać od tej samej kolumny. Nie jest to niezbędne lecz wprowadza do programu pożądany ład wzrokowy. Spróbuj teraz przepisać ładnie poprzednie przykłady. Jeden z nich mógłby wyglądać tak:

modre  equ 148
pole   equ 710

       lda #modre
       sta pole

SŁÓW KILKA O SKOKACH I WARUNKACH

      Innym sposobem definiowania etykiety jest umieszczenie jej na początku zwykłego wiersza programu. Etykieta przyjmuje wtedy wartość adresu pamięci pod którym znajduje się po przetłumaczeniu kod opatrzonego nią rozkazu. Na przykład we fragmencie:

      lda #modre
      jmp tutaj
      sta litery
tutaj sta ramka

instrukcja STA LITERY nie wykona się ponieważ rozkaz skoku JMP wymusi dalsze wykonywanie programu od adresu TUTAJ a jest to adres rozkazu STA RAMKA. Program nie zaburzony rozkazami skoków wykonuje się rzecz jasna z góry na dół Pamietaj że każda używana etykieta musi być dokładnie raz zdefiniowana.

      O wiele przydatniejsze i częściej używane są rozkazy skoków warunkowych. Procesor realizuje je pod określonym warunkiem, mogą się więc wykonywać lub nie. Przykładem takiego rozkazu jest BEQ, który zadziała wtedy, gdy poprzedzająca go operacja porównania liczb wykazała ich równość. Rozkaz CMP porównuje liczbę w akumulatorze z argumentem w pamięci, przyczym żadna z tych liczb nie ulega zmianie. Format tego rozkazu jest taki sam, jak LDA. Poniższy program oczekuje na naciśnięcie dowolnego klawisza, do tego czasu bowiem komórka 764 zawiera liczbę 255, a wiec procesor wciąż powraca do rozkazu oznaczonego etykietą "czekaj". Gdy zawartość komórki 764 się zmieni, rozkaz skoku zostanie zignorowany.

klaw   equ 764
nic    equ 255

czekaj lda klaw
       cmp #nic
       beq czekaj
       lda #nic
       sta klaw

Ostatnie dwa wiersze usuwają kod naciśniętego klawisza, aby nie został on przyjęty przez program, pod którego kontrolą testujemy swoje przykłady. Puste wiersze w programie polepszają jego czytelność, dzieląc tekst na logiczne fragmenty. W asemblerach, które nie przyjmują pustych linijek można postawić w pierwszej kolumnie znak komentarza. Pożądane są także krótkie opisy trudniejszych fragmentów programu czy poszczególnych rozkazów. W QA wiersz komentarza rozpoczyna się od znaku "*", komentarze można też umieszczać w wierszach programu, na końcu, oddzielając je od rozkazu conajmniej jedną spacją. Komentarze nie są asemblowane, nie wydłużają wynikowego kodu programu, a tylko poprawiają jego zrozumiałość jak tu:

*------------------*
*ten program czeka *
* na naciśnięcie   *
* klawisza RETURN  *
*------------------*

* definicje
klaw   equ 764
return equ 12
nic    equ 255

* pętla oczekiwania
czekaj lda klaw
       cmp #nic
       beq czekaj

* naciśnięto klawisz
       ldx #nic
       stx klaw
       cmp #return
       bne czekaj

* gotowe
       rts powrót
       end koniec

      Skok warunkowy BNE odbywa się w przypadku, gdy poprzedzająca go operacja porównania wykazała nierówność. W rzeczywistości mechanizm skoków warunkowych jest nieco bardziej skomplikowany a zarazem bardziej elastyczny niż w podanym wyżej naiwnym objaśnieniu. Rozkaz skoku bada bowiem nie poprzednią instrukcję do której przecież nie może wrócić, lecz stan pewnego specjalnego rejestru procesora zwanego rejestrem znaczników. Znacznik należy rozumieć jako jednobitową komórkę mogącą przyjmować jeden z dwóch stanów: 0 lub 1. Wspomniany rejestr może być traktowany zarówno jako jeden bajt danych,jak też jako osiem oddzielnych znaczników. Rozkazy BEQ i BNE badają znacznik Z (zero) i w zależności od jego stanu skok się odbędzie lub nie. Niektóre inne rozkazy mogą wpływać na stan różnych znaczników. Na przykład rozkaz CMP ustawia znacznik Z (wpisuje 1) gdy porównywane liczby są równe, a kasuje go ( wpisuje 0 ) w przeciwnym przypadku. Zauważ że dzięki temu skok warunkowy nie musi być bezpośrednio następnym rozkazem po instrukcji porównania, o ile tylko dzielące je rozkazy nie naruszają interesującego nas znacznika. BEQ realizuje skok, gdy Z=1, BNE zaś, gdy Z=0.

BYWAJĄ TEŻ OPERACJE ARYMETYCZNE

      Innym rozkazem,który zmienia stan znacznika Z jest DEX, rozkaz artmetyczny, który zmniejsza zawartość rejestru X o 1. Jeśli wynikiem tego działania jest 0, Z zostanie ustawiony w przeciwnym razie skasowany. Poniższy program oczekuje około 2 sek. badając zegar systemu, który zmienia wskazanie co 1/50 s.

zegar equ 20
sek   equ 50
sek2  equ sek+sek

      ldx #sek2
czek1 lda zegar
* czekanie na zmiane
czek2 cmp zegar
      beq czek2
* zmniejsz licznik
      dex
      bne czek1

      Jak łatwo zgadnąć, jest też rozkaz, który zwiększa zawartość rejestru X (nazywa się INX). Znajdziemy także analogiczne rozkazy dla rejestru Y: DEY i INY. Wszystkie one ustawiają znacznik Z, jeśli wynikiem jest 0. Gdy już o symetri mowa, mamy też rozkazy CPX i CPY działające tak jak CMP, porównujące zawartość odpowiedniego rejestru indeksowego z zawartością komórki pamięci. Rozkazy zwiększania i zmniejszania znajdują zastosowanie w tzw. iterakcjach czyli fragmentach programu zapętlonych warunkowym skokiem wstecz dla wielokrotnego wykonania tej samej operacji. Tym sposobem można, na przykład,ustalić liczbę bajtów zerowych w obszarze pamięci od adresu 794 do 830.

hatabs equ 794
ile    equ 36

       ldx #0       liczba zer
       ldy #0       numer bajtu
badaj  lda hatabs,y
       bne nie0
       inx          zwieksz, gdy 0
nie0   iny          nastepny bajt
       cpy #ile
       bne badaj

* tu X zawiera liczbe zer.

Rozkaz LDA HATABS,Y odwołuje się do pamięci w sposób szczególny. Pobiera bowiem bajt nie spod adresu HATABS, lecz dalszego o liczbę zawartą w rejestrze Y. Dla przykładu, jeżeli w Y jest liczba 5 to pobrany zostanie piąty bajt tablicy HATABS. Fragment pamięci, do którego odwołujemy się tym sposobem (zwanym adresacją indeksową) można rozumieć jako tablicę bajtów o wielkości wyznaczonej zakresem zmian rejestru indeksowego. Tu pora na dygresje o liczebnikach porządkowych. Podczas odwołań do pamięci przyjmujemy że początkowym numerem jest zawsze numer 0. Tablica HATABS, zawierająca,jak wiadomo informacje o obsługiwanych przez system urządzeniach zewnętrznych ma długość 36 bajtów ponumerowanych od 0 do 35. Takie też wartości przyjmuje kolejno rejestr Y. Jasne jest chyba dlaczego nie można zacząć liczenia od 1: efektywny adres odwołania do pamięci jest sumą adresu podanego wprost (HATABS) i liczby w rejestrze. Początkowy bajt jest przecież pod adresem HATABS+0!. I jeszcze raz o rozkazie BNE: naprawdę oznacza on "skocz gdy nie 0". Dlatego pozwala wykryć zerowość argumentu w powyższym przykładzie (rozkaz LDA ustawia znacznik Z, oznaczający zerowy wynik operacji). Opisane wcześniej w sposób uproszczony działanie rozkazu CMP polega w istocie na wykonaniu odejmowania (którego wynik nigdzie się nie zapisuje) argumentu od zawartości A i dlatego wynik 0 oznacza równość tych liczb.

KRÓTKIE PRZYPOMNIENIE

      Dotychczas nabyte wiadomości powinny wystarczyć do napisania takiego programu:

       opt 21   opcje
       org 1152 skad

*---deklaracje
zegar  equ 20
ramka  equ 712
klaw   equ 764
nic    equ 255

*---zapamietaj kolor
       ldx ramka
*---mrugaj ramka
mrugaj lda zegar
       sta ramka

*---sprawdz klawisz
       ldy klaw
       iny (255+1=0)
       beq mrugaj

*---skasuj klawisz
       ldy #nic
       sty klaw

*---odtworz kolor
       stx ramka

*---koniec
       rts
       end

ODROBINA NUDNEJ, ALE KONIECZNEJ TEORII NA TEMAT LICZB

      System dziesiętny, stosowany z upodobaniem przez ludzi, z racji budowy ich kończyn, charakteryzuje się tym (i stąd bierze swą nazwę), że liczba różnych cyfr, (a więc i liczb możliwych do przedstawienia jedną cyfrą) jest równa 10. Chcę tu zwrócić uwagę, że liczba jest pojęciem abstrakcyjnym istotnie różnym od jej zapisu na papierze, będącym tylko jej interpretacją wizualną w postaci ciągu znaków, zwanych cyframi. Dobór tych znaków zależy od umowy i może być (a nawet bywa) różny, choć opisuje tę samą liczbę. Dlatego nie będziemy mówić o liczbach dziesiętnych i dwójkowych, lecz o zapisie dwójkowym, dziesiętnym itd. Istota liczby nie zmienia się bowiem w zależności od sposobu jej przedstawienia.

      We wnętrzu komputera liczby są zapisywane w obwodach elekrycznych za pomocą napięć o ustalonych, łatwych do porównania, poziomach.Pojedyńczym elementem liczby w komputerze jest przerzutnik, czyli obwód mogący trwać w jednym z dwóch stanów: "włączony" i "wyłączony". Odpowiednikiem graficznym takiego elementu jest pojedyńczy znak (cyfra) z dwuznakowego repertuaru, np."H" i "L" albo "-" i "+" itd. Łatwo spostrzec, że jeden taki element pamięciowy, zwany bitem, jest w stanie "udźwignąć" niewiele różnych liczb (dokładnie dwie). Jeśli jednak potraktować kilka bitów wspólnie jako zapis jednej liczby, możliwości te szybko rosną. Grupa ośmiu bitów może przyjąć 256 różnych stanów. Taką grupę przyjęto nazywać bajtem.

      W języku asemblera możemy przedstawiać liczby zarówno dziesiętnie, zgodnie z powszednią praktyką, jak i dwójkowo (bitowo). Do zapisu dwójkowego stosujemy cyfry "0" i "1", co dobrze wspomaga intuicję, a cały ciąg cyfr poprzedzamy znakiem "%". Taki zapis stosuje się szczególnie wtedy, gdy trzeba odwołać się do poszczególnych bitów jakiegoś bajtu, np. w celu włączenia silnika magnetofonu, co jest związane z trzecim bitem bajtu 54018.

      lda #%00110100
      sta 54018 start

włącza silnik, zaś

      lda #%00111100
      sta 54018 stop

wyłącza go. Pamiętajmy, że bity są numerowane od prawej strony, a skrajny ma numer 0. Z uwagi na zakres wartości będziemy dzielić liczby na "krótkie" i "długie" krótkimi nazywamy te z liczb, które dają się zapisać przy pomocy jednego bajtu a więc od 0 do 255. W potocznej formie będziemy na nie mówili "bajt". Liczby długie przyjmują wartości z zakresu 0 do 65535, a dla ich wyrażenia komputer potrzebuje dwóch bajtów. Takie liczby nazywamy nieraz "adresami" jako, że ich zakres odpowiada przestrzeni adresowej naszego komputera. Dwubajtowy fragment pamięci potrzebny dla pomieszczenia adresu zwie się potocznie "słowem". Potocznie nie akcentuje się różnic znaczenia zwrotów "słowo" ,"adres", "liczba długa", nierzadko będziemy ich używać zamiennie. Zawsze jednak gdy będzie mowa o słowie np. pod adresem 560 pamiętajmy, że zajmuje ono dwa kolejne bajty: 560 i 561.

OPERACJE LOGICZNE

      Manipulacja silnikiem magnetofonu pokazaną wyżej metodą zakłada, że pozostałe bity mają takie właśnie wartości, jak w przykładzie. Jeśli jest inaczej, oprócz włączenia silnika może spowodować to niepożądane skutki uboczne. Aby tak się nie stało, trzeba "poruszyć" tylko ten jeden wybrany bit. Rozkazy AND, ORA, i EOR działają na odpowiednie bity akumulatora i argumentu w pamięci według znanych ze szkoły "tabelek prawdy":

bit w Abit w pamięciwynik ANDwynik ORAwynik EOR
00000
01011
10011
11110

Wynik operacji pozostaje w akumulatorze. A zatem:

ster_a equ 54018
bit_3  equ %00001000
bit_3_ equ %11110111

       lda ster_a
       and #bit_3
       sta ster_a

wyzeruje trzeci bit rejestru sterujacego portu A, czyli włączy silnik magnetofonu, natomiast:

       lda ster_a
       ora #bit_3_
       sta ster_a

wyłączy magnetofon, nie ruszając pozostałych bitów. Taki układ zer i jedynek służący do operacji na wybranych bitach bajtu, nazywa się maską.

AND zeruje te bity rejestru których odpowiedniki w masce są zerami, resztę pozostawia bez zmian.
ORA ustawia (na 1) te bity,których odpowiedniki w masce są jedynkami, a pozostałe się nie zmieniają.
EOR natomiast zmienia zera na jedynki a jedynki na zera w tych miejscach gdzie bity maski są jedynkami.

      O tych bitach argumentu, które chronione są przed zmianą, mówimy, że są zamaskowane. Oczywiście, ze względu na symetrię operacji logicznych, nie jest ważne, czy właściwy argument jest w pamięci, a maska w akumulatorze, czy na odwrót. Wymienione tu rozkazy wpływają na stan dwóch znaczników:

Z - jest ustawiony, gdy wszystkie bity wyniku są zerami.
N - do niego kopiowany jest najstarszy bit wyniku.

Zwykle zamiast definiować dwie maski dla jednego bitu, zadowalamy się jedną:

ster_a equ 54018
bit_3  equ %1000

       lda ster_a
       and #255-bit_3
       sta ster_a

dla włączenia silnika. Zastosowno tu dwa uproszczenia:

  • początkowe zera każdej liczby można pominąć
  • zaś 255 (dwójkowo %11111111) po odjęciu maski daje jej "negatyw"
      Bardzo dowcipnie działa rozkaz BIT, który ustawia znacznik Z tak, jakby operacja AND między akumulatorem a komórką pamięci, argumenty wszakże nie ulegają zmianie. Dodatkowo szósty bit bajtu z pamięci przenoszony jest do znacznika V, a siódmy do N. W tej grupie wymienia się też zwykle jednoargumentowe rozkazy przesuwające bity, choć mogą też być rozumiane jako mnożenie i dzielenie liczby przez 2. Są cztery takie rozkazy: ASL, LSR, ROL, ROR. Te z nich, które na końcu mają "L", przesuwają bity w lewo, dwa pozostałe w prawo. Pojedyńcze wykonanie rozkazu przesuwa wszystkie bity o jedną pozycję w stosownym kierunku, np. ASL umieszcza na pozycji 1 ten bit, który dotychczas miał numer 0, bit 1 przenosi się na pozycję drugą i tak dalej. Przykładowo:

      lda #%00110101
      asl @

spowoduje przekształcenie liczby w akumulatorze w %01101010. Na opróżnionej skrajnej pozycji w przypadku rozkazów ASL i LSR umieszczane jest zero, natomiast rozkazy ROL, ROR umiesczają tam dotychczasową zawartość znacznika C. Wszystkie cztery rozkazy przenoszą "wychodzący" bit do znacznika C. Symbol "@" oznacza, że argument operacji znajduje się w akumulatorze. Te cztery bowiem (i tylko te!) rozkazy mogą odnosić się do komórki pamięci lub akumulatora.

CO WYNIKA ZE ZNACZNIKA?

      Przedstawić już pora kompletny zestaw znaczników. Zgodnie z miejscem zajmowanym w rejestrze procesora są to:

NV1BDIZC

N, V, Z, C są znacznikami operacji, natomiast B, D, I opisują tryb pracy procesora. Oto omówienie znaczników:

N - kopia 7 bitu wyniku
V - przeniesienie, lub pożyczka między 6 i 7 bitem
1 - nieużywany, większość odmian procesora ma tu 1
B - zerowany na skutek przerwania zewnętrznego poza tym 1
D - tryb dziesiętny wykonywania rozkazów ADC i SBC
I - blokada przyjmowania przerwań IRQ
Z - 1 gdy wszystkie bity wyniku są zerami, kiedy inaczej 0
C - przeniesienie z 7 bitu wyniku (lub brak pożyczki przy SBC)

      Wyjątkowy wpływ rozkazu BIT na znaczniki opisano wyżej. N i Z są aktualizowane przez wszystkie rozkazy artmetyczne, logiczne i przesyłające wartosc do rejestru. Znacznik V zmienia sie tylko pod wpływem rozkazów ADC, SBC, PLP, BIT. Na znacznik C wpływają tylko: ADC, SBC, PLP, ROL, ROR, ASL, LSR. Należy pamiętać, że przesyłanie z rejestru do pamięci nie zmienia żadnych znaczników. Każdy z rozkazów skoków warunkowych kieruje się w swym działaniu wartością jakiegoś znacznika:

rozkazskocz, gdy...
BNE
BEQ
Z=0
Z=1
BPL
BMI
N=0
N=1
BCC
BCS
C=0
C=1
BVC
BVS
V=0
V=1

JESZCZE O TRYBACH ADRESOWANIA

      Aby wyświetlić napis na ekranie, zastosujemy poznaną już konstrukcję pętli:

ekran equ 48192
      ldy #15
pisz  lda #'a'
      sta ekran,y
      dey
      bpl pisz

UWAGA: Jeśli komputer był uruchomiony bez wciśniętego klawisza OPTION, etykiecie ekran trzeba nadać wartość 40000 a nie 48192. Fakt, że obraz w komputerze Atari nie ma sztywno przydzielonego obszaru pamięci komplikuje nieco wyświetlanie jeśli chcemy, by tekst był widoczny w każdej sytuacji. Aktualny adres pamięci obrazu zawarty jest w komórkach 88 i 89. Możemy zatem napisać tak:

adres equ 88

      ldy #15
pisz  lda #'a'
      sta (adres),y
      dey
      bpl pisz

Taki zapis: "sta (adres),y" oznacza zapis bajtu z akumulatora pod adres otrzymany ze słowa leżącego pod adresem "adres", zwiększonego o zawartość rejestru Y. Taki sposób dostępu do pamięci nazywamy adresowaniem pośrednim indeksowanym. Pomyślmy jeszcze chwilę o powyższym przykładzie: jeśli np. w 88 i 89 siedzi adres 20000 to kolejne znaczki zostaną umieszczone pod adresem 20015, 20014, 20013, itd aż do 20000. W ten sposób możemy operować na adresach nam jawnie nie znanych (znanych tylko komputerowi). Zwodniczo podobny, ale zgoła różny w działaniu jest tryb adresowania pośredniego z rejestrem indeksowym X, sta (adres,x) [zwróć uwagę na nawiasy] znajduje adres argumentu w słowie pod adresem o zawartość x dalszym niż "adres". Załóżmy, że komórki 100 i 101 zawierają liczbę 400, komórki 120 i 121 liczbę 500, w rejestrze X jest 20, w rejestrze Y jest 20

      sta (100,x)    odwoła się do komórki o adresie 500.
      sta (100),y    odwoła się do komórki o adresie 420

Nawias oznacza: weź słowo spod... Przecinek oznacza: dodaj zawartość rejestru... Ostateczny wynik jest adresem argumentu. Część liczbowa tych rozkazów jest liczbą krótką, zapisywaną w jednym bajcie, a zatem słowo zawierające adres musi leżeć między adresem 0 a 255. Ten obszar pamięci nazywa się stroną "zerową". Tryb adresowania (adres,x) jest znikomo pożyteczny, w odróżnieniu od drugiego, stosowanego bardzo często. Nietrudno zauważyć, że fragment pamięci możliwy do objęcia przy zmianach rejestru Y jest niewielki (co najwyżej 256 bajtów), gdy trzeba odwołać się do, powiedzmy, 768 bajtów, robimy to tak:

* adres ekranu
ekran equ 88

* komorki 204, 205 nie sa uzywane przez system
adres equ 204

*---kopiowanie adresu
      lda ekran
      sta adres
      lda ekran+1
      sta adres+1
*---zapelnienie ekranu
      ldx #3 (bo 3*256=768)
*-petla po 256 bajtow
l_256 ldy #0
*-petla po 1 bajcie
l_1   lda #'x'
      sta (adres),y
      iny nastepny bajt
      bne l_1

      inc adres+1
      dex nastepne 256?
      bne l_256

      Warto zauważyć, że modyfikować trzeba tylko starszy bajt słowa "adres", ponieważ zmiany rejestru Y pokrywają cały możliwy zakres zmian młodszego bajtu.

STRONY PAMIĘCI, STRONA ZEROWA

      Stroną pamięci nazywamy taki jej obszar, którego adresy mają jednakowy starszy bajt. Na przykład dla komórek od 256 do 511 starszy bajt adresu jest równy 1, gdy młodszy zmienia się od 0 do 255. Starszy bajt wyznacza więc numer strony, młodszy numer bajtu na stronie. Jest oczywiście 256 różnych stron, wszak cała pamięć ma: 256*256=65536 bajtów. Ta ze stron, której numer jest równy 0, zwie się stroną zerową. Wiele rozkazów procesora odnosi się właśnie do tej strony. Adres takich rozkazów zostaje skrócony do jednego bajtu, przez co wykonują się szybciej od tych z pełnymi (długimi) adresami.

CÓŻ TO SĄ WŁAŚCIWIE TE TRYBY ADRESOWANIA?

      Tryb adresowania to inaczej sposób, metoda na poinformowanie procesora, do którego miejsca pamięci ma się przy wykonywaniu rozkazu odwołać. Asembler poznaje ten tryb ze sposobu zapisu argumentu, np.

      ADC 1410

to rozkaz dodania do akumulatora zawartości komórki pamięci o adresie 1410. Niewątpliwie jednak, o ile zadbamy, by w rejestrze X była liczba 10, taki sam efekt wywoła rozkaz:

      ADC 1400,X

Jeśli zaś wiemy, że słowo pod adresem 204 zawiera liczbę 1200, a rejestr Y 210, to możemy napisać nawet:

      adc (204),y
lub
      adc 1200,y

z tym samym skutkiem. Wybór sposobu adresowania zależy od naszej inwencji, która z koleji napędzana jest przez potrzeby. W istocie język maszynowy, który jest wynikiem działania asemblera, ma odrębne rozkazy dla każdego trybu adresowania. Nie ma przeto jednego stałego kodu operacji, np. ADC, lecz wiele odrębnych rozkazów ADC, z których asembler wybiera właściwy, na podstawie analizy tekstu programu. Ale to już jego zmartwienie, nie nasze. Nie wszystkie co prawda rozkazy akceptują każdy tryb adresacji, ale nie sądzę, by warto sobie tym zaprzątać głowę. Jeżeli jakiś tryb adresowania jest nielegalny dla danego rozkazu, to asembler poinformuje nas o tym. Warto tylko wiedzieć, jakim zestawem trybów w ogóle dysponujemy (odnosi się to rzecz jasna tylko do rozkazów z argumentami). Oto zestaw trybów dostępnych dla danych:

      1 - natychmiastowy - "#"

np. LDA #10, musi to być liczba jednobajtowa. Argument stanowi część rozkazu Nielegalny z rozkazami, które zmieniają wartość argumentu (np. INC, DEC rozkazy zwiększania i zmniejszania bajtu w pamięci).

      2 - Bezwzględny długi

czyli z argumentem leżącym gdziekolwiek w pamięci, np. LDA 580. Najprostrzy dopuszczalny z większością rozkazów. Rozkaz maszynowy zawiera prócz bajtu kodu (identyfikującego rozkaz) dwubajtowy adres argumentu. Teoretycznie argument może leżeć nawet na stronie zerowej, lecz w QA jest to trudne do uzyskania, bo dla adresów mniejszych od 256 asembler wybiera tryb krótki.

      3 - Bezwzględny krótki

czyli z argumentem leżącym na stronie zerowej, np. LDA 9. Asembler automatycznie wybiera odpowiedni kod rozkazu, zależnie od argumentu. Rozkaz maszynowy tego typu jest krótszy i wykonuje szybciej od swego długiego odpowiednika, ponieważ zawiera tylko jeden ( młodszy ) bajt adresu (starszy bajt zawsze jest zerem).

      4 - Indeksowy długi

czyli z argumentem leżącym gdziekolwiek w pamięci. Adres,podany w rozkazie zwiększany jest dodatkowo o liczbę zawartą w rejestrze X lub Y. Są zatem dwie odmiany takiego rozkazu np:

      LDA 560,X
i
      LDA 560,Y

Rejestr, którego symbol stoi po przecinku, zwie się w tym kontekście rejestrem indeksowym.

      5 - Indeksowy krótki

czyli z argumentem leżącym na stronie zerowej, którego adres zmodyfikowany jest dodatkowo zawartością rejestru X lub Y. Są zatem dwie odmiany takiego rozkazu, np.

      LDY 32,X
i
      LDY 32,Y

      Zwróć uwagę na dwa fakty, które mogą zaskoczyć poczatkującego programistę. Po pierwsze: wiele rozkazów dopuszcza taki tryb z rejestrem indeksowym X lecz tylko nieliczne z Y (dokładnie dwa LDX i STX). Dlatego program w którym często używamy np. rozkazu LDA 12,Y będzie dłuższy od analogicznego, w którym zastąpiono te rozkazy przez LDA 12,X. Wynika to z faktu, że asembler zastosuje dla LDA 12,Y jedynie dostępny tryb indeksowy długi, dopisując zera w starszej połówce adresu.

      Po drugie (i ciekawsze): procesor odwołuje się rozkazem krótkim ZAWSZE do strony zerowej, a zatem w wyniku wykonania rozkazów:

      ldx #100
      lda 200,x

w akumulatorze znajdzie się zawartość komórki pamięci o adresie 44, a nie 300, jak niektórzy byliby skłonni sądzić. Po prostu procesor zawsze zeruje starszy bajt adresu argumentu. Natomiast nawet starzy wyjadacze bywają niekiedy zaskoczeni faktem, że rozkazy

      ldy #100
      lda 200,y

umieszczą jednak w akumulatorze bajt spod adresu 300.Po prostu nie istnieje krótka odmiana rozkazu LDA 200,Y i asembler, nieświadom naszych intencji użyje tu trybu z adresem dwubajtowym.

      6 - Pośredni indeksowy

czyli z argumentem, którego adres znajduje się nie w rozkazie, lecz w słowie na stronie zerowej określonym przez argument. Są dwie odmiany tego rozkazu różniące się zastosowanym rejestrem indeksowym, oraz niestety,także innymi szczegółami. Odmiana z rejestrem X, np. LDA (20,X) odwołuje się do pamięci poprzez tablicę adresów, która mieści się na stronie zerowej (oznacza to, że fragment strony zerowej traktowany jest jako tablica adresów, a nie, że z założenia są tam jakieś sensowne dane) Jeśli więc napisać:

      ldx #6
      lda (88,x)

to w akumulatorze znajdzie się bajt spod adresu, którego adres siedzi w komórkach 94 i 95. Jeśli to wydaje Ci się dziwne i zawiłe i mało przydatne, to całkowicie się z tym zgadzam. Można na palcach jednej nogi wyliczyć zastosowania tego rozkazu. Myślę, że w większości programów nie został użyty ani razu. Natomiast odmiana z rejestrem Y, np. LDA (20),Y jest bodaj najulubieńszym rozkazem wszystkich programistów aseblerzystów. Odwołuje się on bowiem do tablicy bajtów, której adres siedzi na stronie zerowej w słowie określonym przez argument. Tak więc fragment programu:

      ldy #6
      lda (88),y

umieści w akumulatorze szósty bajt z pamięci ekranu.Adres pośredni dla tych rozkazów MUSI leżeć zawsze na stronie zerowej.

JAK NALEŻY PĘTLIĆ?

      W dotychczasowych przykładach pokazano kilka różnych konstrukcji pętli (iteracji). Wybór właściwego sposobu zapętlania jest umjejętnością ważną, być może nawet podstawową. Uruchamiając poniższe przykałdy nie zapomnij (jak poprzednio) o rozkazach OPT, ORG itd. Dla liczby przebiegów mniejszej od 128 np. wobec 66 znaków, które mają pojawić się na ekranie, postępujemy najprościej:

ekran equ 88 (2)
ile   equ 66

      lda #'a'
      ldy #ile-1
l     sta (ekran),y
      dey
      bpl l

kierunek od 65 do 0 pozwala uniknąć końcowego porównania. Po obsłużeniu (wyświetlanie znaków jest tu oczywiście tylko przykładem) ostatniego zerowego elementu, Y zmienia się z 0 na -1, co spowoduje zakończenie pętli. Dla 256 znaków rejestr indeksowy musi przebiec wszystkie wartości. To również jest proste:

      lda #'b'
      ldy #0
l     sta (ekran),y
      iny
      bne l

gdyż 255 zwiekszone o 1 daje 0. Natomiast dla liczb z przedziału 128-255 sytuacja nieco się komplikuje, ponieważ liczby te są... ujemne! W każdym razie tak to "rozumie" procesor, dla którego oznaką ujemności jest ustawiony 7 (najstarszy) bit liczby. A zatem nie obejdzie się bez dodatkowego rozkazu porównania, jak podczas wyświetlania 192 znaków:

ile   equ 192

      ldy #0
      lda #'c'
l     sta (ekran),y
      iny
      cpy #ile
      bne l

Próba uproszczenia np.

      ldy #ile
      lda #'d'
l     sta (ekran),y
      dey
      bne l

spowoduje zgubienie jednego znaku, ponieważ pętla nie wykona się dla Y=0. Zastąpienie natomiast w tej "poprawionej" wersji BNE przez BPL sprawiłoby, że pętla wykona się tylko raz, bo już na początku siódmy bit Y jest ustawiony. Spróbuj, przekonasz się. W przypadku, gdy jest do zapełnienia znakami cały ekran, trzeba to robić po kawałku. Gdyby jednak użyć metody z poprzedniego odcinka, polegającej na zapełnianiu całych stron, to zostałaby końcówka wymagająca oddzielnej pętli, ponieważ rozmiar ekranu tekstowego nie jest wielokrotnością strony. Lepiej jest obsługiwać ekran w sposób naturalny z ludzkiego punktu widzenia, czyli po wierszu:

wys    equ 24
szer   equ 40
ekran  equ 88
adres  equ 204
czym   equ 'e'

*-przeniesienie adresu ekranu

       lda ekran
       sta adres
       lda ekran+1
       sta adres+1

*-wypelnienie 24 wierszy

       ldx #wys
wiersz lda #czym
       ldy #szer-1
znak   sta (adres),y
       dey
       bpl znak
       clc
       lda adres
       adc        sta adres
       lda adres+1
       adc >szer
       sta adres+1
       dex
       bne wiersz

Fragment od rozkazu CLC do STA ADRES+1 stanowi klasyczny przykład dodawania liczb dwubajtowych. Ponieważ rozkaz dodawania ADC zawsze uwzględnia przeniesienie z poprzedniego dodawnia, można tym sposobem dokonywać operacji artmetycznych na dowolnie długich ciągach bajtów. Przeniesienie (informuje o nim znacznik C) musi zostać wyzerowane przed operacją na pierwszym z bajtów i temu właśnie służy rozkaz CLC W tym przypadku do słowa w pamięci dodaje się argument natychmiastowy: znaczki "<" i ">" oznaczją odpowiednio młodszy i starszy bajt liczby długiej (asembler QA tworzy taki sam kod jak w przypadku znaczka #). Można także analogicznie dodawać dwie długie liczby zamieszkujące pamięć.

PARĘ SŁÓW O LICZBACH W NOTACJI DWÓJKOWEJ

      System dwójkowy,podobnie jak dzisiętny jest systemem pozycyjnym, to znaczy, że wartość reprezentowana przez pojedyńczą cyfrę zależy od pozycji, na której się ta cyfra znajduje. Na przykład w liczbie 472 czwórka oznacza "4 razy 100", siódemka zaś, (choć jako cyfra jest większa) "7 razy 10". Ten mnożnik, który określa ciężar danej cyfry w liczbie nazywa się wagą. Waga n-tej pozycji w dowolnym systemie jest równa podstawie systemu podniesionej do potęgi równej numerowi pozycji. Przypominam, że pozycje (cyfry) numeruje się od prawej strony, a skrajna ma numer 0. Podobnie więc jak w systemie dziesiętnym mamy wagi:

      1=10^0,
      10=10^1,
      100=10^2,
      itd.

Tak w systemie dwójkowym kolejne wagi są:

      1=2^0,
      2=2^1,
      4=2^2,
      8=2^3,
      itd.

Dla rozszyfrowania zatem dowolnego zapisu liczby wystarczy zsumować kolejne iloczyny cyfr i ich wag. Weźmy dla przykładu liczbę %11100001 (% oznacza zapis dwójkowy):

nr pozycji76543210
liczba11100001
wagi1286432168421
iloczyn11100001
********
1286432168421
s=225128+64+32+0+0+0+0+1

Proste prawda? Łatwo zrozumieć, w jaki sposób konstruuje się wszystkie wartości od 0 do 255. W pakiecie QA (którego konsekwentnie używamy we wszystkich przykładach) ustawia się parametry asemblacji przy użyciu rozkazu OPT. Argumentem rozkazu jest liczba, której poszczególne bity opisują tryb pracy asemblera. Oto ich znaczenie:

6543210opis parametru
.....XXzakres listowania:
      0 0 - wyłączone
      0 1 - tylko błędy
      1 0 - cały plik główny
      1 1 - cały assemblowany tekst
....X..listowanie na ekran
...X...listowanie na drukarke
..X....umieszczenie kodu w pamięci
XX.....zapis kodu na dysku (taśmie):
      0 0 - wyłączony
      0 1 - plik binarny (DOS-owy)
      1 0 - dołączenie do pliku
      1 1 - binarna bez nagłówków

      A zatem rozkaz OPT 21, którego użycie w przykładowych programach było zalecane, tłumaczy się: 16+4+1 czyli asemblacja kodu do pamięci i wyświetlenie błędów na ekranie. Ładnie napisany program definjuje parametry osobno:

lsterr equ %00000101
lstall equ %00000110
lstprn equ %00001110
objmem equ %00010000
objdsk equ %00100000

Można je następnie wykorzystywać bez zastanawiania nad znaczeniem bitów np:

       opt objmem+lsterr

*-mruganie na ekranie (migajacy kursor)

klaw   equ 764
nic    equ 255
zegar  equ 20
a_ekr  equ 88
inver  equ %10000000

       org 1152

* przykladowo 5 znak
       ldy #5
* sprawdz klawiature
znowu  lda klaw
       cmp #nic
       bne koniec

* sprawdz zegar
       ldx zegar
       txa
       and #%11111
       bne znowu

* bity 0..4 sa zerami, raz na 32 jednostki, zmien 7 bit znaku,
* oznaczajacy inwersje

       clc
       lda (a_ekr),y
       adc #inver
       sta (a_ekr),y

* poczekaj na zmiane

poczek cpx zegar
       beq poczek

* powtorz wszystko

       jmp znowu

* koniec mrugania

koniec lda #nic
       sta klaw
       rts

* koniec programu

       end

O SPOSOBIE INTERPRETACJI

      Powyższy program mruga znakiem na ekranie, w tym celu pobiera bajt, dodaje do niego maskę bitową, a otrzymaną liczbę wysyła znów na ekran. Oczywiście ta wstrząsajaca różnorodność istnieje tylko w naszym umyśle, procesor 6502 bowiem wykonuje operację zawsze na liczbach i nie ma biedak pojęcia do czego nam one posłużą. Każda taka wyrażona bajtem liczba jest, rzecz jasna numerem jakiegoś znaku, skoro znaków jest właśnie 256. ANTIC, procesor obrazu w ATARI wyświetla w telewizorze znaki, których numery znajdujące się w obszarze pamięci należącej do ekranu (jej adres jest w słowie 88). Tego, że bajt %10000000, stanowiący maskę bitową, jest tym samym, co liczba 128, nie trzeba chyba dogłębnie tłumaczyć. Ponieważ zaś zestaw znaków dzieli się wyraźnie na dwie części po 128 znaków odpowiadających sobie po względem kształtu, a różniących się barwą, nietrudno zgadnąć, dlaczego zwiększenie numeru o 128 powoduje efekt mrugania. Łatwe też jest do wykazania że dwukrotne dodanie liczby 128 (czyli razem 256) przywraca początkowy stan danego bajtu.

      I to zamykałoby ten pierwszy trzyczęściowy odcinek kursu programowania procesora 6502.