Każdy człowiek ma jakieś zainteresowania lub pasje, takie zajęcia, które są dla niego najważniejsze,
najcenniejsze. Jedni zbierają znaczki pocztowe inni oddają się eksperymentowaniu w zakresie elektroniki. W ostatnich
czasach „starożytne” pojęcie elektroniki, które kojarzy się z zapachem unoszącej się kalafonii i łączeniem tranzystorów,
oporników i kondensatorów, coraz bardziej oddala się od obecnego spojrzenia na tematykę. Rewolucja, jaką
wniosło zastosowanie mikroprocesora spowodowało wręcz rewolucję w możliwościach. Coś, co kiedyś było z pogranicza
wręcz magii, staje się obecnie wręcz codziennością. Spróbujmy się rozejrzeć wokół i dostrzec jakieś urządzenie
elektroniczne realizujące funkcję bardziej złożoną niż ładowarka do telefonu by nie miało ono
wewnątrz mikrokontrolera.
Nowoczesne mikrokontrolery mają takie możliwości, że „papcie zrywa”, jednak może zamiast gonić za
nieuchwytnym, zatrzymać się i z zainteresowaniem spojrzeć wstecz i pochylić się nad czymś, co odcisnęło swój
ślad na dalsze dzieje niektórych wynalazków. To one między innymi zasiewały w umysłach szalone koncepcje,
popychały świat do przodu. Wręcz kultowe procki z rodziny Z80 nawet po wielu latach nadal absorbują
wiele umysłów. To od nich zaczęła się moja przygoda z prockami i po iluś latach zataczam wielkie koło,
by do nich wrócić. Co to będzie … nie wiem, koncepcje ulegają ewolucji, jednak stanowią jakąś ciągłość. To co było
kiedyś stało się inspiracją dla dnia dzisiejszego, natomiast dzień dzisiejszy jest „źródłem” dla dnia jutrzejszego.
Z tego wszystkiego nasuwa się jeden wniosek: nie ma ograniczeń i wszystko jest możliwe (choć niektórzy
twierdzą, że nie da się wywrócić wojskowego hełmu na drugą stronę).
Dotychczasowy efekt badawczy trochę nie wypalił, więc zostaje odłożony trochę na późniejsze
czasy, a obecnie, by nie tracić czasu poświęciłem swój czas drugiej połowie. Soft jest nieodłącznym
elementem składowym każdego diwajsa zawierającego procki.z80s-i01.jpg
KONCEPCJACzasami „specjalne” tematy wymagają specjalnego potraktowania i specjalnych narzędzi. A jak wiadomo, narzędzia, to rzecz ważna, bo bez nich to tylko można urobić się po łokcie i później zostanie tylko pozżymać się. Tylko na kogo? No więc by nie mieć powodów do narzekania, warto wcześniej trochę pomyśleć. No i tak urodziła się koncepcja specjalizowanego narzędzia do tworzenia oprogramowania – kompilatora asm dla kultowego proca. Niby takich w sieci internetowej jest … dużo, to może jednak warto pomyśleć nad własnymi rozwiązaniami, nad własną wizją rozwiązania określonych potrzeb.
Tu występują ściśle określone wymagania. Takim nadrzędnym i istotnym jest to, by istniała możliwość wygenerowania kodu programu, który jest umieszczony w określonej lokacji adresowej, ale jest „wylinkowany” na inny adres. Chodzi o to, by powstał kawałek binarnego kodu, który ma zostać przepisany w inny region i tam ma być sprawnie działający. Niby można zrobić jakąś „plombę” na tą okoliczność, typu napisać program zaadresowany na docelowy obszar i później zrobić z jego kodu wstawkę do innego programu. No trochę złożona operacja...
Skoro jednak wybrałem sobie taką ścieżkę, w której planuję wykorzystanie odpowiednich narzędzi, może warto zadbać, by było ono maksymalnie użyteczne. Taka drobna „eskalacja wymagań” zaowocowała kolejnymi zachciankami i możliwościami. Oprócz możliwości jakie daje kompilacja warunkowa (coś, co w języku C jest standardem), jedną z takich zachcianek jest możliwość używania nawet złożonych wyrażeń jako operandy instrukcji oraz danych wbijanych w kod za pomocą odpowiednich dyrektyw. Mając za sobą napisanych tysiące wierszy w języku asm, można dostrzec kilka typowych problemów, których rozwiązanie można niejako zautomatyzować. Trochę przemyśleń, trochę pracy... i powstała jakaś wstępna wersja, którą podzieliłem się z naszą koleżanką
taszą. Ona dodała swoich 5 groszy do całości, nawet spowodowała drobną rewolucję w koncepcji. Uwzględniając jej sugestie, postał w „lazarus” program realizujący założoną funkcjonalność, jednak jego podstawowym elementem jest „niemy” komponent realizujący funkcję kompilatora. W oparciu o ten komponent można utworzyć program w wersji „okienkowej”, jak i w wersji „konsolowej”. Łącząc wszystkie koncepcje, można zaryzykować stwierdzenie, że poniższy program jest naszym wspólnym dzieckiem.
PODSTAWOWA SKŁADNIAPodstawowa składnia zapisów instrukcji jest wzorowana na ogólnie przyjętej, niemniej jest kilka „prywatnych” myków. Wzorem wielu języków programowania, tu nie występuje rozróżnienie pisowni małymi i wielkimi literami. Wszystkie dyrektywy zaczynają się od znaku kropki. W operandach instrukcji mogą wystąpić odwołania do etykiet w programie lub do stałych. Sama etykieta jest nazwą (słowem) innym niż słowo kluczowe (lub słowo stanowiące rozszerzenie na potrzeby rozszerzenia możliwości nie będące słowem kluczowym w dotychczasowym ujęciu – słowa kluczowe będą opisywane w przy okazji opisu nowych możliwości). Etykieta może być zakończona znakiem dwukropka („:”) będącym separatorem pomiędzy etykietą a instrukcją (dyrektywą). Znak dwukropka nie jest obowiązkowy (jak jest, to jest, jak nie ma, to również żadna tragedia). Podobnie ma się sprawa akumulatora w instrukcjach arytmetycznych i logicznych. W asm procka Z80 są instrukcje dotyczące operacji 8-bitowych (gdzie występuje rejestr akumulatora A) lub 16-bitowych (gdzie rolę akumulatora pełni para rejestrów HL), które mają ten sam mnemonik (przykładem jest instrukcja ADD). Z tego powodu w instrukcjach tych konieczne jest jawne określenie rejestru pełniącego rolę akumulatora (rejestr A lub rejestr HL). W każdym innym przypadku (operacji 8-bitowych) domyślnym rejestrem występującym w operacji jest rejestr A, co ma swoje odbicie w zapisie instrukcji, gdzie rejestr ten może zostać pominięty w zapisie (bo jest domyślny). W zaproponowanej wersji, istnieje możliwość zapisu rejestru A jako operandu operacji (pomimo, że jest domyślnym) lub go pominięciu. Kompilator nie jest wrażliwy na zapis (jak jest zapisany, to OK, jak nie ma, to również jest OK). Stałe tekstowe mogą być ujęte w apostrofy lub znaki cudzysłowu, jednak na obu końcach stałej tekstowej mają być identyczne. Stałe liczbowe mogą mieć zapis binarny, dziesiętny, ósemkowy lub szesnastkowy. System zapisu jest zdeterminowany znakiem kończącym liczbę (zapisanym na styk z cyframi). Brak jawnego wskazania oznacza, że dana stała jest zapisana w systemie dziesiętnym. Akceptowane przez kompilator znaki determinujące system zapisu są następujące:
- B (b) – oznacza zapis binarny,
- O (o) lub Q (q) – oznacza zapis ósemkowy,
- D (d) – oznacza zapis dziesiętny,
- H (h) – oznacza zapis szesnastkowy.
Każda stała liczbowa zaczyna się od znaku cyfry. Na oznaczenie cyfr w systemie szesnastkowym używane są litery A .. F (a..f).
WYRAŻENIAJako operandy instrukcji lub dyrektyw mogą występować wyrażenia. Jako wyrażenie rozumiane jest dowolne wyrażenie (z użyciem operatorów arytmetycznych lub logicznych), w którym występują odwołania do innych stałych lub etykiet. Można wyróżnić dwa typy wyrażeń: policzalne w danej chwili (chwili użycia) lub niepoliczalne w danej chwili. Policzalność jest związana ze znajomością wartości stałych (wartość stałej jest znana) lub etykiet (wartością etykiety jest jej adres w przestrzeni adresowej → etykieta może wystąpić dalej w programie źródłowym, toteż w danej chwili jej adres nie jest znany, co implikuje, że wyrażenie, w którym jest użyta etykieta jest niepoliczalne). Poprawnie napisany program ma wszystkie operandy policzalne na zamknięciu programu. Typowo, w języku asm nie dopuszcza się, by w wyrażeniach występowało kilka etykiet. Właściwie, to nie widzę powodu, by takie coś było niedopuszczalne. Jeżeli kompilator będzie dysponował analizą ogólnie pojętego wyrażenia, to nie ma powodu, by „ograniczać jego wolność”. W końcu każda etykieta to jakaś liczba (adres w przestrzeni jest liczbą), więc możliwe jest wykonanie obliczenia dowolnego wyrażenia logiczno-arytmetycznego. Można dyskutować nad sensem przykładowo instrukcji (pobrać do rejestru HL iloczyn adresów dwóch etykiet):
Czy taki zapis ma sens? Może ma, może nie ma, to jest wyłącznie kwestia interpretacji i intencji piszącego.
Przyglądając się instrukcjom, można zauważyć, że niektóre z nich mają operandy jako stałe liczbowe, przykładowo numer bitu w operacjach bitowych (SET <numer bitu> , A). Jeżeli kompilator jest wyposażony w analizator wyrażeń, to dlaczego ma istnieć ograniczenie, gdzie <numer bitu> musi być jawnie wpisaną liczbą. Z punktu widzenia kompilatora, jest to policzalne wyrażenie o wartości od 0 do 7 włącznie. Toteż zapisy:
są całkiem legalnymi instrukcjami, gdyż w momencie ich analizy wszystkie szczegóły są znane, a to pozwala wygenerować właściwy kod instrukcji. Można się zastanawiać po co jest taka możliwość? Czasem odpowiedź jest bardziej zaskakująca niż można się spodziewać: bo to jest ograniczanie możliwości, bo to jest wyjścia poza ogólnie przyjęte schematy, bo to pozwala pomyśleć inaczej niż każdy by się tego spodziewał, bo inne rozwiązanie tematu w kompilatorze stanowi wyjątek, a wyjątki to... zawsze wyjątki. Reasumując niektóre operandy instrukcji muszą być wyrażeniami policzalnymi w danej chwili, większość zaś musi być policzalna na zakończenie programu.
Będąc w temacie wyrażeń, to w wyrażeniu mogą być zastosowane jawne stałe (liczbowe lub tekstowe jedno lub dwuznakowe co odpowiada stałej 8-bitowej lub stałej 16-bitowej), stałe jako identyfikatory stałych. Obok stałych mogą wystąpić „zmienne”, identyfikatory etykiet, których wartością jest adres w przestrzeni adresowej. Naturalnym jest, że etykieta może być już znana (mieć określony adres) lub nieznana (jeszcze nie występująca). W zilogowych instrukcjach operand może być 8-bitowy lub 16-bitowy i to wynika w kontekstu użycia. Jeżeli wyrażenie jest policzalne, jego wartość jest wstawiona do generowanego kodu. W przeciwnym wypadku w kod wchodzi stała 0FFH lub 0FFFFH (tworzy miejsce na docelową wartość, która zostanie uzupełniona w chwili, gdy wyrażenie stanie się policzalne, jeżeli będzie niepoliczalne, to wygeneruje błąd a w kodzie zostaną same FF'y). Rzecz jasna, kontrolowany jest zakres, w jakim ma się zmieścić wartość wyrażenia.
Przykład:
nie ma powodu by kompilator awanturował się w przypadku powyższej instrukcji, jeżeli wartość etykiety Label (jej adres) jest mniejsza niż 256 pomimo że w ogólnym przypadku adres etykiety jest 16-bitowy.
Inny przykład, gdzie etykieta Label może mieć dowolną wartość, to zapis jak poniżej:
pozwala na wsparcie przy obsłudze przerwań (część adresu należy wpisać do rejestru I).
Oprócz identyfikatorów stałych i etykiet w wyrażeniu mogą wystąpić operatory arytmetyczne oraz logiczne. Kompilator rozpoznaje następujące operatory:
- „+” – operator dodawania arytmetycznego, standardowo operator dwuargumentowy (a+b); analizator rozpozna zastosowanie operatora jako jednoargumentowego (+a) i w tym przypadku nie wygeneruje żadnych operacji, jest to tożsamość,
- „-” – operator odejmowania arytmetycznego, standardowo operator dwuargumentowy (a-b) jednak analizator rozpozna zastosowanie jako operatora jednoargumentowego (-a) i w tym przypadku zrealizuje negację arytmetyczną,
- „*” – operator mnożenia arytmetycznego, występuje jako operator dwuargumentowy (a*b)
- „/” – operator dzielenia arytmetycznego (jako dzielenie całkowitoliczbowe), występuje jako operator dwuargumentowy,
- „&” alternatywnie AND – operator iloczynu logicznego, występuje jako operator dwuargumentowy (a & b, a and b),
- „|” alternatywnie OR – operator sumy logicznej, występuje jako operator dwuargumentowy (a | b, a or b),
- „^” alternatywnie XOR – operator logiczny EXOR, występuje jako operator dwuargumentowy (a ^ b, a xor b),
- „~” alternatywnie NOT – operator negacji, występuje jako operator jednoargumentowy (~ a, not a),
- „<” alternatywnie SHL – operator przesunięcia logicznego w lewo, występuje jako operator dwuargumentowy (a < b, a shl b),
- „>” alternatywnie SHR – operator przesunięcia logicznego w prawo, występuje jako operator dwuargumentowy (a > b, a shr b).
Dodatkowo w wyrażeniach mogą występować wywołania określonych funkcji. Analizator rozpoznaje następujące funkcje (jest ich więcej i będą sukcesywnie później wyjaśnione):
- hi ( a ) – jednoargumentowa funkcja dająca jako wynik wartość starszego bajtu w wartościach dwubajtowych,
- lo ( a ) – jednoargumentowa funkcja dająca jako wynik wartość młodszego bajtu w wartościach dwubajtowych,
- mod ( a , b ) – dwuargumentowa funkcja obliczenia arytmetycznego typu modulo: reszta z dzielenia wyrażenia a przez wyrażenie b,
- same ( a ) – „sztuczna” funkcja tożsamościowa (o czym za chwilę).
W wyrażeniach może wystąpić dowolne zagłębienie nawiasów (oczywiście każdy nawias otwierający musi mieć nawias zamykający). Takie podejście czasami może doprowadzić do niejednoznaczności. Przykładowo:
LD HL , ( A + B ) * ( C + D )
skoro za przecinkiem występuje nawias, to kompilator potraktuje to jako instrukcję typu LD HL,(nnnn) i powyższy zapis wygeneruje błąd kompilacji (część A+B jest wyrażeniem, dalej nawias zamykający a pozostała część *(C+D) jest syntaktycznie niepoprawna. Pomimo, że wygląda to jak wygląda, intencją autora jest zapis typu: LD HL,nnnn, a wyrażenie nnnn jest zapisane w postaci ( A + B ) * ( C + D ), gdzie nawias wchodzi jako część wyrażenia. Na tą okoliczność została wprowadzona do kompilatora jednoargumentowa funkcja SAME(x). Wyrażenie SAME((A+B)*(C+D)) nie zaczyna się od znaku nawiasu, więc kompilator nie będzie miał problemów z rozpoznaniem wariantu instrukcji, a sama funkcja nie wpływa na wartość wyrażenia.
Zapisy:
LD HL , same ( ( A + B ) * ( C + D ) )
LD HL , ( same ( ( A + B ) * ( C + D ) ) )
zostaną poprawnie zinterpretowane: jako LD HL,nnnn oraz jako LD HL,(nnnn).
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.