Gruzińskie Noce 03, czyli Nerdy Nights po polsku
Gruzińskie Noce 03, czyli Nerdy Nights po polsku
« dnia: Maj 25, 2016, 13:13:21 »
Tydzień 3: Assembler i pierwszy program


Assembler 6502

bit - najmniejsza jednostka danych. Ma dwie wartości: 1(on) albo 0(off), jak pstryczek od oświetlenia.

Bajt - 8 bitów złożonych razem daje Bajt. Można na nim zapisać liczbę od 0 do 255. Dwa Bajty to 16 bitów i mogą przedstawiać liczbę od 0 do 65535. W Bajcie bity są numerowane od prawej strony do lewej.

instrukcja - pojedyncza komenda wykonywana przez procesor. Instrukcje są wykonywane po kolei.


Konstrukcja Kodu

W assemblerze jest 5 głównych elementów. Niektóre z nich muszą mieć odpowiednie wcięcia, żeby assembler rozumiał je poprawnie.

Dyrektywy
Dyrektywy to polecenia, które wysyłasz assemblerowi, żeby na przykład umieszczał kod w pamięci. Zaczynają się od . czyli kropki i muszą być poprzedzone wcięciem. Tabulator, cztery spacje, dwie spacje... Przykładowa dyrektywa każe assemblerowi rozpocząć kod w miejscu pamięci $8000, która to jest w sekcji ROM'u gry:
    .org $8000

Etykiety
Etykieta wyrównana jest do lewej krawędzi i kończy się dwukropkiem : na końcu. Etykietą organizujesz sobie kod i czynisz go czytelniejszym. Assembler przetłumacza sobie etykietę na konkretny adres.
    .org $8000
MaFunkcja
Podczas gdy assembler znajdzie taki zapis przypisze MaFunkcja do $8000. Czyli jeśli np. w kodzie będzie zapis:
    STA MaFunkcja
Zmieni ją na:
    STA &8000

Mnemoniki/Opcode'y
JMP z ang. jump, czyli "skocz" do kodu, LDA jest od Load itd. i tak samo jak dyrektywy są poprzedzone wcięciem (tab, spacje). Dokładamy do przykładu mnemonik (opcode) który każe mu przeskoczyć do etykiety o nazywie MaFunkcja.
  .org $8000
MaFunkcja:
  JMP MaFunkcja

Operandy
Operandy to dodatkowe informacje dla mnemonik (opcode'ów). Mnemoniki (opcode'y) mają od jednej do trzech operand. W przykładzie operandem jest #$FF:
   .org $8000
MaFunkcja:
   LDA #$FF
   JMP MaFunkcja

Komentarze
Komentarze mają ci pomóc zrozumieć co się dzieje w kodzie. Jeśli wrócisz do fragmentu kodu pisanego dawno temu - uratują ci życie, a co najmniej wiele jego godzin. Nie wstawiaj ich wszędzie (no chyba, że na samym początku zabawy z programowaniem) bo zanieczyszczą ci notatki zamiast pomóc. Komentarze zaczynają się średnikiem ; i są zupełnie ignorowane przez assembler.
  .org $8000
MaFunkcja:        ;ładuje FF do akumulatora
  LDA #$FF
  JMP MaFunkcja

Powyższy kod tworzy nieskończoną pętlę, która będzie ładowała wartość $FF do akumulatora, wracała do MaFunkcja i ładowała $FF do akumulatora, wracała...


Opis Procesora 6502

6502 to 8-bitowy, procesor 6502 z 16-bitową szyną adresową. Ma dostęp do 64KB pamięci bez stosowania przerzucania pamięci. W NESie ta pamięć podzielona jest na RAM, PPU/Audio/Dostęp do kontrolera oraz ROM gry.
$0000-0800 - wewnętrzny RAM, 2KB w NESie
$2000-2007 - porty dostępu do PPU
$4000-4017 - porty dostępu do Audio i kontrolera
$6000-7FFF - opcjonalnie WRAM wewnątrz cardridge'a
$8000-FFFF - ROM gry na cartridge'u.


Opis Assemblera 6502

Język assembler dla 6502 zaczyna się 3 znakową instrukcją "opcodem". Jest 56 instrukcji z czego często używać będziesz ok 10 z nich. Wiele instrukcji będzie miała jakąś wartość za opcode'm, którą możesz zapisać w dziesiętnym lub heksie. Jeśli tej wartości nie poprzedza # to jest to adres. Czyli:
LDA #$05 to załaduj wartość 5, a
LDA $0005 to załaduj wartość, która przechowywana jest pod adresem $0005.


Rejestry 6502

Rejestr to miejsce wewnątrz procesora, które przechowuje wartość jakąś. 6502 ma trzy 8 bitowe rejestry i rejestr statusu, którego będziesz używał. Dodatkowe rejestry nie są opisane w tym poradniku.

Akumulator
Akumulator to główny 8-titowy rejestr służący to ładowania, przechowywania, porównywania i liczenia danych. Poniżej kilka najczęściej używanych operacji:
LDA #$FF  ;ładuje wartość hexową $FF (dziesiętnie to 256) do A
STA $0000 ;przechowuje kumulator w, internal RAM

Rejestr indeksu X
Rejestr indeksu X (X) to kolejny 8 bitowy rejestr, zazwyczaj używany do liczenia lub dostępu pamięci. W pętlach będziesz go używał do śledzenia ile razy już pętla była wykonywana, podczas używania A do przetwarzania danych. Niektóre częste operacje to:
LDX $0000 ;wczytaj wartość z miejsca pamięci $0000 do X
INX          ;zwiększ(INkrementuj) X   X = X + 1

Rejestr indeksu Y
Rejestr indeksu Y (Y) działa praktycznie tak samo jak X. Niektóre instrukcje (nie są opisane w tym poradniku) działają tylko z X, a nie z Y. Przykładowe zwykłe operacje:
STY $00BA ;przechowaj(STore) Y do miejsca pamięci $00BA
TYA          ;przenieś (transfer) Y do Akumulatora.

Rejestr Statusu
Rejestr Statusu zawiera flagę z informacją o ostatniej istrukcji. Na przykład podczas odejmowania możesz sprawdzić, czy wynikiem jest zero.


Zestaw instrukcji 6502

Często używane mnemoniki (opcode'y) Ładuj/Zapisz
LDA #$0A   ; Ładuj wartość 0A do akumulatora A
                ; liczbowa część mniemoniki (opcode'u) może być wartością lub też adresem
                ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

LDX $0000  ; Ładuj wartość spod adresu $0000 do rejestru indeksu X
                ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

LDY #$FF   ; Ładuj wartość $FF do rejestru indeksu Y
                ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

STA $2000  ; Zapisz wartość z akumulatora A do adresu $2000
                ; liczbowa część mniemoniki (opcode'u) musi być adresem

STX $4016  ; Zapisz wartość z X w $4016
                ;  liczbowa część mniemoniki (opcode'u) musi być adresem

STY $0101  ; Zapisz Y w $0101
                ;  liczbowa część mniemoniki (opcode'u) musi być adresem

TAX        ; Przenieś wartość z A do X
              ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

TAY        ; Przenieś A do Y
              ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.
TXA        ; Przenieś X do A
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

TYA        ; Przenieś Y do A
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.


Często używane mnemoniki (opcode'y) matematyczne
ADC #$01   ; Dodaj z przenoszeniem
                ; A = A + $01 + przenoszenie
                ;  jeśli wartość jest zerem, zostanie ustawiona flaga zera.

SBC #$80   ; Obejmij z przenoszeniem
                ; A = A - $80 - (1 - przenoszenie)
                ;  jeśli wartość jest zerem, zostanie ustawiona flaga zera.

CLC        ; Wyczyść flagę przeniesienia w rejestrze statusu
             ; zazwyczaj powinno być to wykonane przed dodawaniem (ADC)

SEC        ; Ustaw flagę przeniesienia w rejestrze statusu
             ; zazwyczaj powinno być to wykonane przed odejmowaniem (SBC)

INC $0100  ; Inkrementuj(zwiększ) wartość w adresie $0100
                ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

DEC $0001  ; Dekrementuj(zmniejsz) $0001
                ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

INY        ; INkremenruj rejestr Y
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

INX        ; INkremenruj rejestr X
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

DEY        ; DEkrementuj Y
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

DEX        ; DEkrementuj X
             ; jeśli wartość jest zerem, zostanie ustawiona flaga zera.

ASL A      ; Przesunięcie bitowe w lewo
              ; Przesuń wszystkie bity o jedno miejsce w lewo
              ; jest to mnożenie razy 2
              ;  jeśli wartość jest zerem, zostanie ustawiona flaga zera.

LSR $6000  ; Przesunięcie bitowe w prawo
                ; Przesuń wszystkie bity o jedno miejsce w lewo
                ; jest to dzielenie przez 2
                ;  jeśli wartość jest zerem, zostanie ustawiona flaga zera.

Często używane opcode'y porównujące
CMP #$01   ; Porównaj A do wartości $01
                ; wykonuje dzielenie, ale nie podaje wyniku
                ; w zamian możesz sprawdzić rejestr statusu:
                ; równy, mniejszy, czy większy

CPX $0050  ; Porównaj X do wartości spod adresu $0050

CPY #$FF   ; Porównaj Y do wartości $FF


Często używane opcode'y przepływu wiadomości
JMP $8000  ; skocz do $8000, kontynuuj odtwarzanie kodu w tym miejscu

BEQ $FF00  ; Odgałęzienie gdy równe, kontynuuj odtwarzanie kodu w tym miejscu
           ; najpierw powinieneś wykonać CMP, które wyczyści lub ustawi flagę zero
           ; następnie BEQ sprawdzi flagę zero
           ; jeśli zero jest ustawione (wartości są równe) kod skoczy do $FF00 i będzie kontynuowany od tamtego miejsca
           ; jeśli zero jest wyczyszczone (wartości nie są równe) nie ma skoku, kod jest kontynuowany poprostu w kolejnej linijce

BNE $FF00  ; Odgałęzienie jeśli nie równe - odwrotność powyższego, opposite above, skok wykonuje się, gdy flaga zero jest wyczyszczona


Struktura kodu

Wstęp
Ta część będzie zawierać sporo informacji, bo od razu ustawimy wszystko, żeby uruchomić pierwszy program na NES'a. Część kodu możesz po prostu wkleić, a co od czego jest dokładnie opisane będzie później. Głównym celem jest odpalenie w ogóle pierwszego programu.

Nagłówek iNES
16 bajtowy nagłówek przedstawia emulatorowi / konsoli podstawowe informacje o mapperze, pionowym/poziomym mirroringiem obrazu i rozmiarach PGR i CHR. Możesz te informacje wstawić na samym początku swojego pliku asm.
  .inesprg 1   ; 1x 16KB bank kodu PRG
  .ineschr 1   ; 1x 8KB bank danych CHR
  .inesmap 0   ; mapper 0 = NROM, bez zmian banków
  .inesmir 1   ; mirroring tła (pionowy)

Bankowanie
NESASM grupuje wszystko w 8KB banki kodu i 8KB banki grafiki. By wypełnić 16KB miejsca w PRG potrzebne są 2 banki. Numeracja zaczyna się od zera (czyli 0 jest pierwszy, a 1 to drugi). Musisz wskazać assemblerowi w którym miejscu w pamięci ma się zaczynać każdy z poszczególnych banków.
  .bank 0
  .org $C000
; i dalej trochę kodu

  .bank 1
  .org $E000
; tu też dalej kod

  .bank 2
  .org $0000
; a tu grafiki

Dodawanie plików
Pliki zewnętrzne są często używane przy grafikach i poziomach. Stosuje się do tego dyrektywę incbin. Dane nie będą od razu używane, ale jest to potrzebne do dopasowania pliku .NES z nagłówkiem iNES.
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB1

Wektory
Są trzy specyficzne sytuacje, kiedy procesor NESa przerywa zwykły ciąg kodu i skacze do nowego miejsca. Wektory zapisane w pamięci ROM PGR wskazują te miejsca procesorowi. Dwie z trzech wektorów będą opisane w tym poradniku.
Wektor NMI - wykonywany raz na klatkę (wideo) jeśli jest włączony. PPU informuje procesor, że zaczyna się VBlank i jest gotowe do aktualizacji grafiki.
Wektor RESET - wykonywany zawsze podczas włączania i resetowania konsoli.
Wektor IRQ - wykonywany dla pewnych chipów w mapperach lub przerwań audio, nie będziemy o tym mówić.
Te trzy zawsze muszą pojawić się w kodzie we właściwej kolejności. Dyrektywa .dw określa słowo maszynowe (Data Word) (1 słowo = 2 bajty):
  .bank 1
  .org $FFFA     ;tu się zaczyna pierwszy z trzech wektorów
  .dw NMI        ;kiedy wystąpi NMI (raz na klatkę, jeśli jest włączony) procesor skoczy do etykiety NMI
  .dw RESET      ;podczas uruchomienia konsoli i resetowania procesor skacze do etykiety REDET
  .dw 0          ;zewnętrzne przerwania IRQ sobie darujemy w tym poradniku

Uzupełniając Program
Twoje pierwsze, fascynujące dzieło będzie wyświetlać ekran pełen... jednego koloru! Aby tego dokonać trzeba wprowadzić ustawienia PPU. Wpisuje się je w adres $2001. Ciąg 76543210 to kolejne numery bitów, od 7 do 0. Te 8 bitów bajta zapiszemy pod $2001.
PPUMASK ($2001)
76543210
||||||||
|||||||+- Skala Szarości (0: zwykłe kolory; 1: WSZYSTKIE palety
|||||||      z 0x30, wyświetlać będą wszystko na czarno - biało)
|||||||      zwróć uwagę, że podkreślenie kolorów wciąż działa, nawet jeśli szarość jest włączona!
||||||+-- Wyłącz ucinanie 8 pixeli tła z lewej strony ekranu
|||||+--- Wyłącz ucinanie 8 pixeli sprite'ów z lewej strony ekranu
||||+---- Włącz renderowanie tła
|||+----- Włącz renderowanie sprite'ów
||+------ Podkreśl czerwony (i przygaś pozostałe kolory)
|+------- Podkreśl zielony (i przygaś pozostałe kolory)
+-------- Podkreśl niebieski (i przygaś pozostałe kolory)
W tym programie bity 7, 6, 5 zostaną użyte do ustawienia koloru ekranu:
  LDA %10000000   ;podkreśl niebieski
  STA $2001
Forever:
  JMP Forever     ;nieskończona pętla

Podsumowując
http://www.nespowerpak.com/nesasm/background.zip
http://www.nespowerpak.com/nesasm/NESASM3.zip
http://www.the-interweb.com/serendipity/exit.php?url_id=627_id=90
http://www.the-interweb.com/serendipity/exit.php?url_id=627&entry_id=90
Pobierz i wypakuj background.zip. Cały ten powyższy kod jest w pliku background.asm. Upewnij się, że plik mario.chr i background.bat są w tym samym folderze co NESASM3, potem dwuklik w background.bat. Włączy się NESASM3 i powinien utworzyć background.nes. Otwórz plik NES w FCEUXD SP by zobaczyć swoje kolorowe tło! Zedytuj background.asm żeby zmienić intensywność bitów 7-5 w celu zmiany tła na zielony lub czerwony.

Możesz zacząć Dubug... (z menu Tools w FCEUXD SP) żeby zobaczyć tok swojego kodu. Wciśnij Step Into, wybierz Reset z NES menu, później klikaj Step Into by odtwarzać instrukcje po kolei. Z lewej są adresy pamięci, następny jest opcode w hex, który aktualnie odtwarza 6502. Zajmuje to 1 - 3 bajty. Dalej jest kod, który pisałeś z pominiętymi komentarzami i etykietami przetłumaczonymi na adresy. Górna linijka to instrukcja, która będzie wykonywana jako następna. W sumie jak dotąd nie ma za dużo kodu, ale później debugger przyda się dużo bardziej.

Literatura uzupełniająca:
Oryginał [ENG]:
http://www.nintendoage.com/forum/messageview.cfm?catid=22&threadid=4440

Dodatek od siudyma:

http://wyslijto.pl/plik/rpldzkbm8c