Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych. Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2013 mgr
Jerzy Wałaszek
|
Współczesne komputery przebyły długą drogę rozwoju od dużych, prymitywnych maszyn z lat 50 i 60-tych ubiegłego wieku do superszybkich komputerów osobistych opartych na najnowszej technologii. Programowanie takich nowoczesnych maszyn jest sztuką ogromnie skomplikowaną, która wymaga od programisty olbrzymiej wiedzy na temat funkcjonowania poszczególnych składników komputera. Wiedzę tę zdobyć nie jest łatwo, a zajmuje to lata.
PMC jest Przykładową Maszyną Cyfrową, czyli prostym komputerem. Jego nieskomplikowana budowa pozwala w szybkim tempie opanować podstawowe składniki i przystąpić bezpośrednio do programowania w kodzie maszynowym – asemblerze. Tak, właśnie kod maszynowy jest podstawą pracy wszelkich komputerów i zrozumienie jego funkcji i zasad działania uważam za kluczowe zadanie każdego programisty. Znajomość pracy komputera na najniższym poziomie sprzętowym znakomicie ułatwia programowanie w każdym języku programowania.
To, co w rzeczywistym systemie jest trudne i skomplikowane, w PMC jest proste i zrozumiałe. Jednocześnie zasady programowania PMC i rzeczywistego komputera są bardzo podobne. Opanowanie programowania PMC ułatwi zatem późniejsze przejście na prawdziwe systemy komputerowe – programista będzie znał podstawowe zasady, które tu i tam są identyczne.
prof. John von Neumann |
Wszystkie współczesne komputery osobiste są zbudowane wg
koncepcji zaproponowanej w latach 40-tych ubiegłego wieku przez profesora
Johna von Neumanna z Uniwersytetu w Princeton. Jego
model zakładał, iż komputer wyposażony będzie w trzy podstawowe składniki:
|
Brak któregokolwiek z wymienionych składników uniemożliwia korzystanie z maszyny cyfrowej. Bez pamięci komputer nie może gromadzić danych oraz wyników obliczeń. Bez układów wejścia/wyjścia nie można się z nim skomunikować, staje się zatem kosztownym przyciskiem do papieru. Bez procesora nie może być wykonywany program i przetwarzane dane.
PMC jest również zbudowana logicznie wg architektury Johna von
Neumanna.
Pamięć komputerowa (ang. computer memory) jest urządzeniem, które służy do przechowywania przetwarzanej informacji, wyników tego przetwarzania oraz programu dla procesora. Zewnętrznie wygląda tak jak po prawej stronie. Czarna, płaska kostka plastikowa z wystającymi metalowymi końcówkami, którymi podawane są odpowiednie sygnały elektryczne sterujące pracą pamięci.
Wewnątrz pamięć komputerowa zawiera tysiące (a nawet miliony) komórek (ang. memory cells). W każdej komórce można zapisać informację w postaci ciągu bitów (zwykle 8 bitów, zwanych bajtem). Cała sztuka polega na zakodowaniu informacji w odpowiedni ciąg zer i jedynek.
Komórki pamięci komputerowej są ponumerowane. Numer komórki nazywamy adresem (ang. memory cell address). Dzięki adresowi procesor może bez problemów uzyskać dostęp do wybranej komórki i odczytać zawartą tam informację lub zapisać nową.
Wyprowadzenia pamięci komputerowej tworzą trzy grupy sygnałów, które nazywamy magistralami (ang. buses). Pierwsza magistrala umożliwia odczyt danych oraz ich wpis do pamięci, jest to magistrala danych (ang. data bus). Druga magistrala przekazuje pamięci adres wybranej komórki, jest to magistrala adresowa (ang. address bus). Trzecia magistrala zawiera różne sygnały sterujące, nazywa się magistralą sterującą (ang. control bus).
Zapis danych odbywa się następująco:
|
Odczyt danych odbywa się następująco:
Pamięć PMC zbudowana jest z 63 komórek 16-bitowych. Komórki mają adresy od 1 do 63. Może stwierdzisz, że nie jest to zbyt wiele – będziesz miał zupełną rację. Jednakże przypominam – PMC jest przeznaczona TYLKO do ćwiczeń, a nie do poważnych zadań. Zapewniam cię, iż 63 komórki to ilość zupełnie wystarczająca do naszych celów.
Ostatnim terminem wartym zapamiętania jest przestrzeń adresowa (ang. address space). Jest to zbiór adresów, które może wykorzystywać procesor do komunikacji z pamięcią. Im większa przestrzeń adresowa, tym więcej danych można umieszczać w pamięci oraz przetwarzać. W przypadku PMC przestrzeń adresowa obejmuje 64 adresy (od 0 do 63). Adres 0 jest zarezerwowany do specjalnych zadań. Pozostałe adresy można dowolnie wykorzystywać. Jeśli spojrzymy na liczby 0...63 od strony binarnej, to zauważymy, iż wszystkie adresy można zapisać przy pomocy 6 bitów w naturalnym kodzie dwójkowym:
000000(2) = 0(10)
111111(2) = 63(10).
Wynika z tego, iż nasza PMC ma 6-bitową magistralę adresową oraz 16-bitową magistralę danych.
Kolejnym elementem w architekturze komputera są urządzenia wejścia/wyjścia lub krótko wejście/wyjście (ang. Input/Output – I/O). Zewnętrznie są to klawiatury, monitory, drukarki, wszelkiego typu urządzenia zapisująco-odtwarzające, itp. Z punktu widzenia mikroprocesora, który nic nie musi wiedzieć o prawdziwej budowie tych urządzeń, stanowią one pewne szczególne pozycje w przestrzeni adresowej, do których można przesłać informację lub z których odczytuje się informację.
Na przykład wyobraźmy sobie, iż wyposażyliśmy nasz komputer w klawiaturę. Każde naciśnięcie klawisza powoduje przesłanie do komputera kodu binarnego tego klawisza. Kod klawisza jest dostępny w specjalnym rejestrze wejścia/wyjścia, który może się nazywać na przykład rejestrem odczytu klawiatury. Dostęp do tego rejestru może być wykonywany w identyczny sposób jak dostęp do komórki pamięci. Po prostu w przestrzeni adresowej procesora nie wszystkie adresy odnoszą się do komórek pamięci. Niektóre oznaczają właśnie takie specjalne rejestry przechowujące informacje pochodzące ze współpracujących z komputerem urządzeń. Jeśli na magistralę adresową procesor wyśle adres takiego rejestru, to logika sterująca komputerem (płyta główna – ang. mother board) nie odwoła się do układów pamięciowych, tylko do wskazanego rejestru.
Oczywiście z punktu widzenia procesora rejestr I/O nie różni się od zwykłej komórki pamięci. Odczyt czy zapis danych odbywa się wg identycznego schematu (co znacznie upraszcza budowę samego procesora).
Co więcej, ten sam adres może wskazywać dwa różne rejestry w
zależności od tego, czy operacja dotyczy zapisu, czy odczytu. Tak właśnie jest w
PMC. Adres 0 (zero) nie oznacza komórki pamięci, lecz
wejście danych (przy odczycie) lub wyjście danych
(przy zapisie). Adres ten nosi nazwę INOUT, która
w PMC
jest zarezerwowana właśnie dla
Zapis informacji do rejestru INOUT powoduje przesłanie znaku do wyświetlacza. Znak ten pojawi się na ekranie. Zatem zapis informacji pod adres 0 oznacza przesłanie danych do urządzenia wyjścia, jakim jest wiersz wyświetlacza. Wyświetlacz w PMC ma postać wiersza o maksymalnej pojemności 64 znaków. Wyświetlacz ten możesz potraktować jak taśmę. Każdy przesłany znak zostaje dopisany do końca taśmy. Jeśli znaki nie mieszczą się w wierszu, to zostają przewinięte w lewo. Znaki drukowalne mają kody od 32 (spacja) do 126. Dodatkowo są rozpoznawane dwa kody:
KOD | 0 | – wyświetlacz jest czyszczony, znika z niego cała treść. |
KOD | 127 | – ostatni znak zostaje skasowany. |
Pozostałe kody (od 1 do 31 i powyżej 127) nie są drukowane.
Z kolei odczyt danych z adresu 0 powoduje próbę pobrania danych z
bufora wejściowego. W buforze tym użytkownik PMC może w trakcie działania
programu umieszczać znaki z klawiatury. Każdy odczyt powoduje pobranie jednego
znaku i zwrócenie jego kodu ASCII. Jeśli bufor zostanie
opróżniony, to odczyt zwróci wartość 0 – w ten sposób można prosto sprawdzić,
czy w buforze skończyły się znaki. Kod znaku ma zawsze 8 bitów, zatem wejście
PMC jest
Adres 0 (INOUT) to jedyny rejestr wejścia/wyjścia w PMC. Wszystkie inne adresy w przestrzeni adresowej odnoszą się do zwykłych komórek pamięci.
Bez wątpienia procesor jest najbardziej skomplikowanym elementem każdego komputera. Musi tak być, ponieważ to właśnie on odpowiada za działanie całego systemu i przetwarzanie danych.
Procesor przetwarza informację pobierając ją z pamięci do wewnętrznych rejestrów, na których wykonuje różne operacje. Wynik jest z powrotem umieszczany w pamięci.
Rejestr (ang. register) jest jakby wewnętrzną mikropamięcią procesora. W rejestrze można umieszczać dane kilkubitowe (zależy to od typu procesora). Następnie procesor może przetwarzać w różny sposób informację w rejestrach (np. dodawać je, mnożyć, dzielić, wykonywać nad nimi operacje logiczne itp.) Takie rozwiązanie wybrano ze względu na szybkość – działania na rejestrach są bardzo szybkie, natomiast pamięć jest stosunkowo wolna, ponieważ procesor musi adresować wybrane komórki i czekać, aż zawartość będzie dla niego dostępna. Dlatego właśnie procesor wyposaża się w wewnętrzne rejestry (komórki pamięci wewnętrznej), w których umieszcza on sobie najczęściej potrzebne mu dane.
Procesor PMC posiada tylko jeden rejestr dla danych zwany akumulatorem (ang. accumulator) – nie, nie chodzi tutaj o baterię, tylko o pewne cechy tego rejestru, które zrozumiemy, gdy przejdziemy do omawiania instrukcji procesora. Do akumulatora można pobrać zawartość dowolnej komórki pamięci. Przy operacjach arytmetycznych i logicznych zawsze jeden z argumentów znajduje się w akumulatorze. Również wynik operacji trafia do akumulatora (akumuluje się). Rejestr akumulatora w PMC mieści 16 bitów. Zwróć uwagę, iż odpowiada to dokładnie rozmiarowi pojedynczej komórki pamięci.
Program dla procesora zbudowany jest z pojedynczych rozkazów, poleceń, które dokładnie instruują go, co ma w danej chwili robić. Rozkazy te mają postać kodów binarnych, liczb, które umieszcza się w pamięci komputera. Procesor odczytuje kolejne kody instrukcji, rozpoznaje zawarte w nich polecenia i wykonuje je.
Ponieważ instrukcje zapisane są w kolejnych komórkach pamięci, to procesor w trakcie wykonywania programu musi pobierać dane z tych komórek. Do tego celu służy mu specjalny rejestr zwany licznikiem rozkazów lub licznikiem programu (ang. program counter). Licznik rozkazów przechowuje adres wykonywanej instrukcji.
Faza pobrania i wykonania rozkazu jest następująca:
Gdy znamy już wszystkie elementy składowe naszej maszyny cyfrowej, to złóżmy je w całość. Otrzymamy następujący system:
Po lewej stronie mamy układ pamięci złożony z 63 komórek 16-bitowych.
W środku są dwa rejestry wejścia/wyjścia. Rejestr WEJŚCIE umożliwia odczyt danych wejściowych zgromadzonych w buforze wejścia. Jeśli bufor jest pusty, odczyt zwraca wartość zero. Rejestr WYJŚCIE umożliwia wyprowadzenie przesłanego doń znaku na wyświetlacz wierszowy. Oba rejestry zajmują ten sam adres w przestrzeni adresowej procesora – adres 0 (zero). Jeśli procesor zapisuje dane pod ten adres, to trafią one do rejestru WYJŚCIA. Jeśli procesor odczytuje dane z tego adresu, to otrzyma on zawartość rejestru WEJŚCIE.
Po prawej stronie widzimy procesor PMC z trzema rejestrami:
– akumulator służy do przetwarzania danych
–
licznik rozkazów służy do adresowania kolejnych
instrukcji w pamięci programu
– rejestr instrukcji
służy do dekodowania pobranego kodu rozkazu.
Elementy składowe komputera połączone są ze sobą za pomocą trzech
magistral:
– magistrala danych
służy do transportowania danych pomiędzy procesorem a pozostałymi składnikami
systemu
– magistrala adresowa
umożliwia wybór komórki pamięci lub rejestrów wejścia/wyjścia, z którymi
procesor chce wymienić dane poprzez magistralę danych
– magistrala
sterująca
przekazuje polecenia odczytu/zapisu od procesora oraz potwierdzenia
wykonania tych operacji przez pozostałe składniki.
Asembler jest częścią symulatora PMC, która zamienia tekst programu zapisanego w postaci symbolicznej na program, który zostanie umieszczony w pamięci. Poniżej opisujemy budowę instrukcji PMC oraz składnię wierszy programu.
Procesor PMC pobiera kolejne instrukcje z pamięci (przy pomocy licznika programu) i umieszcza je w rejestrze instrukcji. Pobrana instrukcja jest analizowana w celu określenia rodzaju operacji do wykonania. Po rozkodowaniu instrukcji procesor wykonuje wskazane polecenie i pobiera kolejną instrukcję.
Aby zrozumieć kodowanie rozkazów w PMC musimy przedstawić instrukcję w postaci binarnej - czyli tak, jak jest ona przechowywana wewnątrz komputera. Ponieważ instrukcje są umieszczane w 16 bitowych komórkach pamięci PMC, to każda z nich zbudowana jest z 16 bitów. Bity te podzielimy na trzy grupy, które będziemy zwać polami (ang. bit field).
Bity instrukcji PMC | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
b15 | b14 | b13 | b12 | b11 | b10 | b9 | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
Pole instrukcji | Pole trybu adreso- wania |
Pole argumentu |
Bity b12...b15 (cztery najstarsze) tworzą pole instrukcji. Od kombinacji bitów w tym polu zależy rodzaj instrukcji, czyli rodzaj operacji, które wykona procesor po rozkodowaniu instrukcji. Cztery bity pozwalają na 16 różnych kombinacji i tyle różnych instrukcji może wykonać procesor PMC.
Dwa następne bity b10 i b11 określają tzw. tryb adresowania. Tryb adresowania jest sposobem pobierania przez procesor argumentu dla instrukcji. Dwa bity pozwalają na cztery różne tryby adresowania.
Pozostałe bity b0...b9 określają argument dla instrukcji. Ich dokładne znaczenie zależy od zawartości pola trybu adresowania.
Procesor PMC potrafi pobierać argument dla rozkazu na cztery różne sposoby. Rodzaj adresowania zapisany jest w postaci dwubitowego kodu w polu tryby adresowania instrukcji PMC. W zależności od kombinacji tych bitów rozróżniamy następujące tryby adresowania:
|
Tryb adresowania akumulatora.
Argument dla instrukcji znajduje się w rejestrze akumulatora. |
||||
|
Tryb adresowania
natychmiastowy. Argument znajduje się w bitach od b0...b9 kodu instrukcji. Bity te są traktowane jako liczba w kodzie U2 (jeśli nie wiesz, co to jest liczba w kodzie U2, to musisz przeczytać odpowiedni rozdział w artykule Binarne Kodowanie Liczb). Ponieważ bitów tych jest 10, to możliwe wartości argumentu w trybie natychmiastowym zawierają się w granicach od -512 do 511. |
||||
|
Tryb adresowania bezpośredni
Argument znajduje się w komórce pamięci, której adres przechowują bity
b0...b5
kodu instrukcji. Pozostałe bity pola argumentu są ignorowane. Jeśli adres
wynosi 0, to argument jest w rejestrze INOUT. |
||||
|
Tryb pośredni Bity b0...b5 kodu instrukcji zawierają adres komórki pamięci, która przechowuje adres właściwego argumentu. Stąd nazwa tryb pośredni - w instrukcji wskazujemy komórkę zawierającą właściwy adres argumentu. Taki tryb znakomicie nadaje się do adresowania danych umieszczonych w tablicy, ponieważ w prosty sposób można zmieniać adres zawarty w komórce. |
Wszystkie podane tryby adresowania argumentu mogą być stosowane z każdą instrukcją PMC (chociaż nie zawsze ma to sens).
Rodzaj instrukcji do wykonania określa 4-bitowe pole instrukcji. Instrukcje są następujące:
|
END arg Instrukcja powoduje
zakończenie wykonywania przez PMC programu. Argument jest pobierany i
zwracany jako wynik instrukcji END
(pojawia się w okienku informacyjnym). Po wykonaniu
tej instrukcji program może być uruchomiony jedynie od nowa - nie ma
możliwości kontynuacji. |
||||||||
|
LDA arg LoaD Accumulator Instrukcja pobiera podany argument do rejestru akumulatora. |
||||||||
|
STA arg STore Accumulator Instrukcje umieszcza zawartość akumulatora w podanym argumencie. Jeśli argument jest adresowany przez tryb 0 lub 1 (akumulatora lub natychmiastowy), to instrukcja nic nie robi. |
||||||||
|
ADD arg Instrukcja dodaje do
akumulatora podany argument. Wynik operacji arytmetycznej i logicznej zawsze
jest umieszczany w akumulatorze. Jeśli argument jest adresowany przez tryb 0
(tryb akumulatora), to w wyniku zawartość akumulatora ulegnie
podwojeniu. Instrukcja nie sprawdza, czy wynik operacji mieści się w |
||||||||
|
SUB arg SUBstract Instrukcja odejmuje od akumulatora podany argument. Jeśli argument jest adresowany przez tryb 0, to w wyniku otrzymujemy 0. Jeśli wynik operacji przekracza zakres 16 bitowych liczb U2, to zostaje obcięty do 16 bitów. |
||||||||
|
MUL arg MULtiply Instrukcja mnoży zawartość akumulatora przez podany argument. Jeśli argument jest adresowany przez tryb 0, to w wyniku otrzymujemy kwadrat poprzedniej zawartości akumulatora. Wynik mnożenia jest zawsze obcinany do 16 bitów w kodzie U2. |
||||||||
|
DIV arg DIVide Instrukcja dzieli zawartość akumulatora przez podany argument. Jeśli argument jest adresowany przez tryb 0, to wynik wynosi 1. Jeśli argument ma wartość 0, to powstaje błąd dzielenia przez 0 i wykonanie programu zostaje zatrzymane z wyświetleniem odpowiedniego komunikatu. |
||||||||
|
MOD arg MODulo Instrukcja oblicza wartość reszty z dzielenia akumulatora przez podany argument. Jeśli argument jest adresowany przez tryb 0, to wynik wynosi 0. Jeśli argument jest równy 0, to powstaje błąd dzielenia przez 0 i wykonanie programu zostaje wstrzymane z wyświetleniem odpowiedniego komunikatu. |
||||||||
|
INC arg INCrement Instrukcja zwiększa o 1 podany argument. Jeśli argument jest adresowany przez tryb 1 (tryb natychmiastowy), instrukcja nic nie robi. Jeśli wynik operacji przekracza zakres 16-bitowych liczb U2, zostanie obcięty do 16 bitów. |
||||||||
|
DEC arg DECrement Instrukcja zmniejsza o 1 podany argument. Jeśli argument jest adresowany przez tryb 1 (tryb natychmiastowy), instrukcja nic nie robi. Jeśli wynik operacji przekracza zakres 16-bitowych liczb U2, zostanie obcięty do 16 bitów. |
||||||||
|
JMP arg JuMP Instrukcja skoku pod adres równy podanemu argumentowi. Wykonanie tej instrukcji polega na umieszczeniu wartości argumentu w rejestrze licznika rozkazów. Dzięki temu następna instrukcja zostanie wykonana nie z następnego adresu, lecz z nowego. Instrukcje skoku umożliwiają tworzenie tzw. pętli oraz rozgałęzień w programie. Chociaż instrukcja może być stosowana z dowolnym trybem adresowania, jednak najczęściej jest stosowany tryb natychmiastowy - adres skoku znajduje się bezpośrednio w bitach pola argumentu instrukcji. |
||||||||
|
JZR
arg Jump on ZeRo Instrukcja wykonuje skok pod adres wskazany argumentem tylko wtedy, gdy akumulator zawiera wartość zero. W przeciwnym razie skok nie zostanie wykonany - następną instrukcją dla procesora będzie instrukcja za rozkazem skoku. Tego typu operacja nazywana jest skokiem warunkowym (ang. conditional jump). |
||||||||
|
JMI arg Jump on MInus Instrukcja wykonuje sok pod adres wskazany argumentem tylko wtedy, gdy akumulator zawiera wartość ujemną. W przeciwnym razie skok nie zostanie wykonany i procesor przejdzie do instrukcji znajdującej się za rozkazem skoku. |
||||||||
|
AND arg Instrukcja wykonuje
operację logiczną AND na bitach akumulatora i argumentu. Wynik jest
umieszczany w akumulatorze. (Jeśli nic nie wiesz na temat
operacji logicznych, to przeczytaj artykuł o Binarnym
Kodowaniu Liczb). |
||||||||
|
ORA arg OR with Accumulator Instrukcja wykonuje operację logiczną OR na bitach akumulatora i argumentu.
Wynik jest umieszczany w akumulatorze. |
||||||||
|
XOR arg eXclusive OR Instrukcja wykonuje operację logiczną XOR na bitach akumulatora i argumentu. Wynik jest umieszczany w akumulatorze. |
Przykład:
Dla przykładu zakodujmy w postaci binarnej instrukcję, która dodaje do zawartości akumulatora liczbę 15. Kod instrukcji dodawania, to 0011. W ten sposób określamy bity pola kodu instrukcji. Liczba 15 jest daną natychmiastową, zatem pole trybu adresowania ma kod 01. Pozostałe dziesięć bitów określa w kodzie U2 liczbę 15 - 0000001111. Łączymy poszczególne pola w całość i otrzymujemy:
0011010000001111 - dodaj do akumulatora liczbę 15. Proste?
Ponieważ zapis bitowy instrukcji PMC jest niewygodny, każda instrukcja posiada 3 literową nazwę, którą nazywamy mnemonikiem. Mnemonik ułatwia zapamiętanie polecenia. W poniższej tabeli zebraliśmy wszystkie instrukcje PMC
Mnemonik | Pole instrukcji | Krótki opis |
---|---|---|
END arg |
0000 | Koniec programu |
LDA arg |
0001 | AC ← arg, PC ← PC + 1 |
STA arg |
0010 | arg ← AC, PC ← PC + 1 |
ADD arg |
0011 | AC ← AC + arg, PC ← PC + 1 |
SUB arg |
0100 | AC ← AC - arg, PC ← PC + 1 |
MUL arg |
0101 | AC ← AC x arg, PC ← PC + 1 |
DIV arg |
0110 | AC ← AC : arg, PC ← PC + 1 |
MOD arg |
0111 | AC ← AC MOD arg, PC ← PC + 1 |
INC arg |
1000 | arg ← arg + 1, PC ← PC + 1 |
DEC arg |
1001 | arg ← arg - 1, PC ← PC + 1 |
JMP arg |
1010 | PC ← arg |
JZR arg |
1011 | Jeśli AC = 0, PC ← arg, inaczej PC ← PC + 1 |
JMI arg |
1100 | Jeśli AC < 0, PC ← arg, inaczej PC ← PC + 1 |
AND arg |
1101 | AC ← AC AND arg, PC ← PC + 1 |
ORA arg |
1110 | AC ← AC OR arg, PC ← PC + 1 |
XOR arg |
1111 | AC ← AC XOR arg, PC ← PC + 1 |
Oprócz rozkazów dla procesora PMC zdefiniowana jest jeszcze tzw,
dyrektywa DAT, która umożliwia umieszczenie danych w komórce pamięci. Dyrektywa
nie jest rozkazem i nie zostaje przetłumaczona na kod instrukcji. Zamiast tego w
bieżącej komórce umieszcza się argument tej dyrektywy. Argument może być liczbą
16-bitową U2 o zakresie od
DAT 115 ;umieszczenie w komórce liczby 115
PMC posiada 4 tryby adresowania argumentu.
Tryb adresowania akumulatora tworzymy nie umieszczając za
instrukcją żadnych danych. Na przykład
ADD
Oznacza dodanie do siebie zawartości rejestru akumulatora.
Uwaga - jeśli zastosujemy rozkaz skoku JMP, JZR lub JMI bez argumentu, to PMC wykona skok pod adres zawarty w akumulatorze. Czasami może to być potrzebne (tzw. skoki wyliczane).
W tym trybie wartość argumentu jest bezpośrednio zakodowana w bitach pola argumentu. Tryb natychmiastowy oznaczamy umieszczając bezpośrednio przed wartością znak #. Na przykład:
LDA #15
oznacza polecenie umieszczenia w akumulatorze liczby 15. Po wykonaniu tej instrukcji akumulator będzie zawierał wartość 15. Inne przykłady:
ADD #8 ;dodanie do zawartości akumulatora liczby 8 MUL #5 ;pomnożenie akumulatora przez 5
Tryb natychmiastowy stosowany jest standardowo z rozkazami skoków:
JMP #26 ;skok do instrukcji umieszczonej pod adresem 26
Pole argumentu zawiera w tym trybie adres komórki pamięci PMC, w której jest właściwy argument. Tryb bezpośredni oznaczamy umieszczając bezpośrednio przed adresem znak $. Na przykład:
LDA $15
oznacza polecenie umieszczenia w akumulatorze zawartości komórki pamięci PMC o adresie 15. Zwróć uwagę, iż nie jest to to samo co poprzednio, ponieważ komórka 15 może zawierać dowolną wartość. Inne przykłady:
STA $0 ;umieść zawartość akumulatora w rejestrze INOUT STA $10 ;umieść zawartość akumulatora w komórce pamięci pod adresem 10 ADD $5 ;dodaj do zawartości akumulatora zawartość komórki pod adresem 5
Zapamiętaj dobrze różnicę pomiędzy trybem natychmiastowym, a bezpośrednim. W trybie natychmiastowym argument jest bezpośrednio podany w instrukcji. W trybie bezpośrednim w instrukcji podajemy adres komórki zawierającej argument:
MUL #6 ;mnóż akumulator przez liczbę 6 MUL $6 ;mnóż akumulator przez to, co zawiera komórka o adresie 6
W trybie tym instrukcja w polu argumentu zawiera adres komórki przechowującej właściwy adres argumentu. Dostęp do argumentu jest zatem pośredni poprzez wskazywaną komórkę. Tryb pośredni oznaczamy umieszczając bezpośrednio przed adresem komórki znak *:
LDA *15
Instrukcja ta oznacza pobranie do akumulatora zawartości komórki pamięci PMC, której adres przechowuje komórka o adresie 15. Jeśli przykładowo w komórce tej jest liczba 25, to do akumulatora trafi zawartość komórki o adresie 25.
Tryb pośredni jest najtrudniejszym do zrozumienia trybem adresowania, lecz z drugiej strony najczęściej się go stosuje przy dostępie do tablic. Zapamiętaj, iż w trybie pośrednim instrukcja zawiera adres komórki, w której jest właściwy adres argumentu.
Posługiwanie się adresami liczbowymi jest dosyć żmudne i nużące. Dlatego programiści wymyślili etykiety. Etykieta jest nazwą, którą dużo łatwiej zapamiętać. W PMC etykiety muszą rozpoczynać się od litery lub od znaku podkreślenia. Następne znaki muszą być literami, cyframi lub znakami podkreślenia. Tylko pierwsze 8 znaków etykiety jest znaczące. Etykieta musi się kończyć znakiem dwukropka.
Etykiety umieszcza się zawsze przed instrukcją lub przed dyrektywą DAT. Etykieta przyjmuje wartość adresu instrukcji, przy której została umieszczona. Umożliwia to później łatwiejsze wykonanie skoku do wybranej instrukcji lub odwołanie się do danych. Na przykład poniższy program sumuje dwie komórki i wynik umieszcza w trzeciej komórce.
DAT 10 ;pierwsza liczba DAT 15 ;druga liczba DAT 0 ;tutaj będzie wynik dodawania LDA $1 ;pobieramy do akumulatora pierwszą liczbę ADD $2 ;dodajemy do niej drugą liczbę STA $3 ;wynik umieszczamy w trzeciej komórce END |
L1: DAT 10 ;pierwsza liczba L2: DAT 15 ;druga liczba WYNIK: DAT 0 ;tutaj będzie wynik dodawania START: LDA $L1 ;pobieramy do akumulatora pierwszą liczbę ADD $L2 ;dodajemy do niej drugą liczbę STA $WYNIK ;wynik umieszczamy w trzeciej komórce END |
Program jest zawsze umieszczany w pamięci PMC począwszy od komórki o adresie 1. Zatem pierwsza instrukcja programu zawsze znajduje się w komórce o adresie 1. Bez etykiet musimy sami pamiętać, w których komórkach są dane i instrukcje. Z etykietami zadanie to mamy rozwiązane w prosty sposób. Do komórek będziemy się odwoływać za pomocą symbolicznych etykiet, zatem nie musimy nawet wiedzieć, pod którym adresem dana komórka się znajduje - zadanie to przejmuje za nas asembler, który tłumaczy tekst programu PMC na odpowiednią zawartość komórek pamięci.
Jako dane w instrukcjach PMC można stosować:
DAT 1745 DAT -15 LDA #33 STA $25 ADD *12 |
DAT 0FFFFH DAT 35AH AND #3FH STA $4CH |
Liczby szesnastkowe (jeśli nic nie wiesz o liczbach szesnastkowych, ósemkowych i binarnych, przeczytaj artykuł o Binarnym Kodowaniu Liczb) kończą się zawsze literką H. Jeśli liczba szesnastkowa rozpoczyna się od cyfry A,B,C,D,E lub F, to cyfrę tę należy poprzedzić cyfrą 0 - w przeciwnym razie asembler będzie sądził, iż jest to etykieta i powstanie błąd.
DAT 777Q DAT 33Q DIV #45Q |
Liczby ósemkowe kończą się zawsze literą Q.
DAT 1100101B DAT 1100B AND #11110000B |
Liczby dwójkowe zawsze kończą się literą B.
DAT "H DAT "A LDA #"B |
Znak jest zawsze poprzedzony cudzysłowem. Znak zostaje zamieniony na kod ASCII i w takiej postaci jest umieszczany w pamięci lub w kodzie instrukcji. Z kilku znaków tylko pierwszy jest znaczący:
LDA #"ABC ;w akumulatorze znajdzie się kod ASCII litery A |
DAT TEKST DAT DANE LDA $CZYNNIK JMP #START |
Jako dane można również stosować etykiety. Wartością etykiety jest zawsze adres komórki, do której etykieta się odnosi.
Tworząc program dla PMC musimy przestrzegać kilku prostych zasad.
W programie można wstawiać dowolną ilość wierszy pustych (w przykładzie wiersz pusty pomiędzy etykietami LICZNIK oraz START):
LICZNIK: DAT 0 START: LDA $LICZNIK ... |
Wiersze puste nie są tłumaczone na zawartość pamięci, nie wpływają zatem na długość programu wynikowego.
Komentarz w PMC rozpoczyna się od znaku średnika, po którym możemy umieścić dowolny tekst. Tekst ten nie jest analizowany przez asembler. Komentarze można umieszczać w dowolnym miejscu wiersza. Od początku komentarza do końca wiersza programu treść jest ignorowana. Komentarze są doskonałym narzędziem do dokumentowania działania programu
;Zmienne programu PRCNT: DAT 10 ;wartość procentu KONTO: DAT 120 ;stan konta |
Jeśli wiersz ma definiować etykietę, to musi się ona znajdować na początku wiersza:
LP1: LDA $LICZNIK ADD #23 STA $LICZNIK JMI #LP1 |
Jeśli w wierszu umieścimy samą etykietę, to definiuje ona pierwszą komórkę z instrukcją, która wystąpi w najbliższym wierszu:
START: LDA $XXX ... |
W tym przykładzie etykieta START wskazuje komórkę z instrukcją LDA $XXX. Nie wolno definiować więcej niż jednej etykiety dla tej samej komórki.
Początkowe spacje są nieznaczące, zatem można je wykorzystywać do pozycjonowania instrukcji programu w tej samej pionowej kolumnie, co znakomicie poprawia czytelność. Porównaj dwa przykłady tego samego programu bez wcięć i z wcięciami. Który jest bardziej estetyczny i czytelny?
WSP_A: DAT -5 WSP_B: DAT 3 X: DAT 0 START: LDA $WSP_B JZR #KONIEC LDA $WSP_A DIV $WSP_B STA $X KONIEC: END |
WSP_A: DAT -5 WSP_B: DAT 3 X: DAT 0 START: LDA $WSP_B JZR #KONIEC LDA $WSP_A DIV $WSP_B STA $X KONIEC: END |
Najprostszy program dla PMC II nawet nie musisz pisać. Po uruchomieniu symulatora w okienku tekstu programu masz:
; Program dla PMC II ; Autor: ; Wersja: ; Data: START: END |
Oczywiście program ten nic nie robi. Po kompilacji i uruchomieniu program natychmiast się zatrzymuje z powodu instrukcji END.
Kolejny program również jest bardzo prosty. Umieszcza ona w rejestrze akumulatora liczbę 133.
START: LDA #133 ;umieść w akumulatorze 133 END ;zakończ program |
Gdy go skompilujemy i uruchomimy, to po zatrzymaniu powinniśmy zauważyć, iż akumulator zawiera wartość 133.
Akumulator | Licznik Rozkazów | Rejestr Instrukcji |
0000000010000101 : 0085 : 133 | 0002 : 2 | 0000 : END |
Zwróć uwagę, iż zawartość akumulatora jest prezentowana przez symulator PMC II na trzy sposoby:
- jako liczba binarna: | 0000000010000101 |
- jako liczba szesnastkowa: | 0085 |
- jako liczba dziesiętna: | 133 |
Dzięki temu rozwiązaniu możesz w prosty sposób interpretować zawartość akumulatora wg potrzeb.
Licznik rozkazów zatrzymał się na adresie 2. Adres ten zawiera instrukcję END, co widzimy w rejestrze instrukcji. To właśnie ta instrukcja zatrzymała dalsze wykonywanie programu.
Program umieszcza liczbę 125 w pierwszej komórce pamięci.
DANE: DAT 0 ;tutaj umieścimy liczbę 125 START: LDA #125 ;umieszczamy najpierw w akumulatorze liczbę 125 STA $DANE ;teraz zawartość akumulatora umieszczamy w komórce 1 END |
Zwróć uwagę, iż przed uruchomieniem programu komórka 1 (o etykiecie DANE) zawiera wartość 0. Wartość tę umieszcza tam dyrektywa DAT. Po wykonaniu programu w komórce tej powinna znaleźć się wartość 125, czyli to, co program tam umieścił.
Adr | Etykieta | Inst | Argument | Pamięć |
00 | INOUT: | |||
01 | DANE: | DAT | 125 | 007D 125 |
02 | START: | LDA | #125 | 147D 5245 |
03 | STA | $DANE | 2801 10241 | |
04 | END | 0000 0 |
Przesłanie danych ma na celu pobranie informacji przechowywanej w jednej komórce i umieszczenie jej w innej. Poniższy program przenosi dane z komórki DANE1 do DANE2:
DANE1: DAT 147 ;tę liczbę skopiujemy do komórki następnej DANE2: DAT 0 ;tutaj będzie skopiowana zawartość komórki poprzedniej START: LDA $DANE1 ;pobieramy zawartość pierwszej komórki STA $DANE2 ;i umieszczamy ją w drugiej komórce END |
Po zakończeniu programu w obu komórkach DANE1 i DANE2 mamy tę samą wartość 147.
Adr | Etykieta | Inst | Argument | Pamięć |
00 | INOUT: | |||
01 | DANE1: | DAT | 147 | 0093 147 |
02 | DANE2: | DAT | 147 | 0093 147 |
03 | START: | LDA | $DANE1 | 1801 6145 |
04 | STA | $DANE2 | 2802 10242 | |
05 | END | 0000 0 |
Operacja zamiany zawartości polega na przesłaniu danych pomiędzy komórkami, tak aby ich zawartości zostały nawzajem zamienione: w komórce pierwszej ma się znaleźć to co było w drugiej, a we drugiej to co było w pierwszej Operacja taka wymaga dodatkowej zmiennej pomocniczej, w której przechowujemy tymczasowo zawartość jednej z komórek. Zamiany dokonujemy w trzech krokach:
Poniższy program zamienia miejscami zawartości komórek DANE1 i DANE2:
DANE1: DAT 15 ;zawartości tych dwóch komórek zostaną DANE2: DAT 188 ;zamienione miejscami X: DAT 0 ;to jest zmienna pomocnicza START: LDA $DANE1 ;przesyłamy DANE1 do X STA $X LDA $DANE2 ;przesyłamy DANE2 do DANE1 STA $DANE1 LDA $X ;przesyłamy X do DANE1 STA $DANE2 END |
Po wykonaniu programu komórki DANE1 i DANE2 mają zamienioną zawartość.
Adr | Etykieta | Inst | Argument | Pamięć |
00 | INOUT: | |||
01 | DANE1: | DAT | 188 | 00BC 188 |
02 | DANE2: | DAT | 15 | 000F 15 |
03 | X: | DAT | 15 | 000F 15 |
04 | START: | LDA | $DANE1 | 1801 6145 |
05 | STA | $X | 2803 10243 | |
06 | LDA | $DANE2 | 1802 6146 | |
07 | STA | $DANE1 | 2801 10241 | |
08 | LDA | $X | 1803 6147 | |
09 | STA | $DANE2 | 2802 10242 | |
10 | END | 0000 0 |
PMC II posiada tylko jedno urządzenie wyjścia - rejestr INOUT, który zajmuje adres 0 w przestrzeni adresowej. Każdy zapis do tego rejestru powoduje przesłanie na wyświetlacz jednego znaku. Poniższy program wyświetla literkę A:
START: LDA #"A ;pobieramy do akumulatora kod literki A STA $INOUT ;przesyłamy go do rejestru wyjścia END |
Po wykonaniu tego programu na wyświetlaczu pojawi się literka A.
A |
Pętla to konstrukcja programowa, w której wybrana grupa instrukcji jest cyklicznie powtarzana. Pętla nieskończona wykonywana jest ciągle, bez końca. Aby zatrzymać program z pętlą nieskończoną musisz posłużyć się przyciskiem STOP. Poniższy program wyświetla ciąg zer i jedynek.
START: LDA #"0 ;pobieramy do akumulatora znak 0 STA $INOUT ;przesyłamy go na wyjście LDA #"1 ;pobieramy znak 1 STA $INOUT ;przesyłamy go na wyjście JMP #START ;skaczemy na początek programu - powtarzamy od nowa |
Cechą charakterystyczną pętli nieskończonej jest instrukcja skoku JMP do początku pętli. Skok ten powoduje ponowne wykonywanie rozkazów zawartych pomiędzy początkiem pętli a instrukcją skoku.
1010101010101010101010101010101010101010101010101010101010101010 |
Cechą odróżniającą komputery od prostych liczydeł jest możliwość podejmowania różnych działań w zależności od sytuacji napotkanej w trakcie obliczeń. Obliczenia niejako mogą być wykonywane różnymi drogami w zależności od wyników operacji poprzednich. Dzięki tym cechom komputer może działać celowo - mądrze, inteligentnie.
W repertuarze rozkazów PMC II mamy dwie instrukcje, których wykonanie zależy od spełnienia pewnego warunku:
JZR - skok do adresu, gdy akumulator zawiera zero
JMI - skok
do adresu, gdy akumulator zawiera wartość ujemną
Wbrew pierwszemu wrażeniu te dwie instrukcje pozwalają badać nawet złożone warunki. Poniżej podajemy odpowiednie przykłady:
Poniższy program bada liczbę przechowywaną w zmiennej DANE. W zależności od wartości tej liczby wyświetla jeden z napisów <0, =0 lub >0.
DANE: DAT -256 START: LDA $DANE ;pobieramy liczbę do akumulatora JZR #ZERO ;czy liczba równa zero? JMI #MINUS ;czy liczba ujemna? LDA #"> ;liczba dodatnia JMP #PISZ ;wyświetlamy tekst ZERO: LDA #"= ;liczba równa zero JMP #PISZ MINUS: LDA #"< ;liczba ujemna PISZ: STA $INOUT ;wypisujemy odpowiedni tekst LDA #"0 STA $INOUT END |
Poniższy program bada zawartość komórki DANE. Jeśli jest ona ujemna, to zmienia ją na dodatnią.
DANE: DAT -68 ;komórka z danymi START: LDA $DANE ;sprawdzamy, czy liczba jest ujemna JMI #MINUS ;jeśli tak, skaczemy do etykiety MINUS END ;jeśli nie, kończymy program. Liczba jest dodatnia MINUS: LDA #0 ;zmieniamy znak liczby odejmując ją od 0 SUB $DANE STA $DANE END |
Ten program bada zawartość dwóch komórek DANE1 i DANE2. W komórce MAX umieszcza większą z nich.
DANE1: DAT 139 ;komórki z danymi DANE2: DAT 278 MAX: DAT 0 ;tutaj program umieści większą z liczb START: LDA $DANE1 ;pobieramy pierwszą liczbę SUB $DANE2 ;testowo odejmujemy od niej drugą liczbę JMI #D2 ;jeśli różnica mniejsza od 0, to DANE2 jest większa LDA $DANE1 ;tutaj większa jest DANE1 JMP #KONIEC D2: LDA $DANE2 ;tutaj większa jest DANE2 KONIEC: STA $MAX ;w MAX umieszczamy większą z liczb END |
Pętla warunkowa powstaje wtedy, gdy wybrana grupa instrukcji jest powtarzana w zależności od spełnienia pewnego warunku.
Poniższy program odczytuje dane z wejścia PMC II. Jeśli bufor jest pusty, to odczyt rejestru daje wartość 0. W takim przypadku program czeka aż na wejściu pojawi się jakaś dane. To jest pierwsza pętla warunkowa - cykliczny odczyt wejścia aż pojawi się na nim dana.
Gdy na wejściu pojawią się dane program wchodzi w drugą pętle warunkową - odczytuje dane i przesyła je na wyjście aż skończą się. W takim przypadku program jest kończony.
START: LDA $INOUT ;pobieramy znak z wejścia JZR #START ;jeśli brak znaku, czekamy aż się pojawi PISZ: STA $INOUT ;odczytany znak przesyłamy na wyjście LDA $INOUT ;pobieramy kolejny znak JZR #KONIEC ;jeśli dane się skończyły, kończymy JMP #PISZ ;w przeciwnym razie kontynuujemy KONIEC: END |
Kolejny program wykorzystuje pośredni tryb adresowania do przesłania zadanego tekstu na wyjście. Tekst jest przesyłany z kolejnych komórek pamięci dotąd, aż zostanie napotkany znak o kodzie 0. Wtedy program kończy swoje działanie.
TEKST: DAT "W ;w kolejnych komórkach umieszczamy DAT "I ;poszczególne literki tekstu witaj DAT "T DAT "A DAT "J DAT 0 ;to jest koniec tekstu TPTR: DAT TEKST ;tutaj mamy adres pierwszego znaku tekstu START: LDA *TPTR ;pobieramy znak tekstu JZR #KONIEC ;jeśli koniec tekstu, kończymy STA $INOUT ;znak przesyłamy na wyjście INC $TPTR ;zwiększamy adres tekstu - następny znak JMP #START ;kontynuujemy pętle z następnym znakiem KONIEC: END |
Pętla iteracyjna jest pętlą warunkową, która wykonuje się zadaną ilość razy. Cechą charakterystyczną pętli iteracyjnej jest licznik zliczający kolejne obiegi. Gdy licznik osiągnie zadaną wartość, pętla kończy działanie.
Poniższy program wyświetla zadaną ilość literek X.
LICZNIK: DAT 15 ;liczba obiegów pętli START: LDA $LICZNIK ;sprawdzamy, czy licznik osiągnął 0 JZR #KONIEC ;jeśli tak, kończymy SUB #1 ;zmniejszamy licznik o 1 STA $LICZNIK LDA #"X ;na wyjście przesyłamy znak X STA $INOUT JMP #START ;kontynuujemy pętlę KONIEC: END |
Drugi program wypisuje na wyświetlaczu wszystkie duże literki od A do Z.
LICZNIK: DAT "A ;wartość początkowa licznika - kod literki A START: LDA $LICZNIK ;licznik przesyłamy na wyjście STA $INOUT SUB #"Z ;sprawdzamy, czy licznik osiągnął wartość Z JZR #KONIEC ;jeśli tak, kończymy INC $LICZNIK ;jeśli nie, przechodzimy do kolejnej literki JMP #START ;i kontynuujemy pętlę KONIEC: END |
Większość komputerów wypisuje liczby wykonując odpowiedni program. W tym celu zamienia się wartość liczby na odpowiedni ciąg znaków. Algorytm jest następujący:
Kolejne od końca cyfry otrzymujemy jako resztę z dzielenia liczby przez podstawę systemu, w którym chcemy zapisać liczbę. Wartość liczby dzielimy przez podstawę. Operację kontynuujemy dotąd, aż liczba osiągnie wartość 0. Wtedy otrzymane cyfry wyświetlamy. Dokładne algorytmy zamiany liczb na ciągi znaków znajdziesz w artykule Binarne Kodowanie Liczb.
Poniższy program wyświetla dziesiętnie zawartość komórki DANE
DANE: DAT 26541 ;liczba do wyświetlenia - dodatnia! CYFRY: DAT 0 ;bufor na 5 cyfr DAT 0 DAT 0 DAT 0 DAT 0 CPTR: DAT 0 ;wskaźnik cyfr w buforze START: LDA #CYFRY ;ustawiamy CPTR na ostatnią cyfrę ADD #4 STA $CPTR ;CPTR -> CYFRY[4] LP1: LDA $DANE ;pobieramy liczbę MOD #10 ;wyznaczamy cyfrę ADD #48 ;dodajemy kod ASCII STA *CPTR ;wstawiamy cyfrę do bufora LDA $CPTR ;przesuwamy się na kolejną cyfrę SUB #1 STA $CPTR LDA $DANE ;liczbę dzielimy przez podstawę 10 DIV #10 STA $DANE JZR #PISZ ;jeśli koniec, wypisujemy bufor JMP #LP1 ;jeśli nie, kontynuujemy pętlę PISZ: INC $CPTR ;CPTR wskazuje o jedną cyfrę wcześniej. Korygujemy LDA *CPTR ;pobieramy cyfrę STA $INOUT ;przesyłamy ją na wyjście LDA $CPTR ;sprawdzamy, czy koniec bufora SUB #CYFRY SUB #4 JZR #KONIEC JMP #PISZ ;jeśli nie, kontynuujemy KONIEC: END |
Odczyt liczb dokonuje się w sposób programowy wg tzw. schematu Hornera:
Dokładne algorytmy odczytu liczb w dowolnym systemie pozycyjnym znajdziesz w artykule Binarne Kodowanie Liczb.
Poniższy program odczytuje z wejścia liczbę (musi być zapisana poprawnie) w postaci ciągu znaków i wyznacza jej wartość w komórce DANE. Tam też zobaczymy wynik po zakończeniu programu.
DANE: DAT 0 ;tutaj znajdzie się odczytana z wejścia liczba X: DAT 0 ;zmienna pomocnicza START: LDA $INOUT ;czekamy, aż na wejściu pojawią się znaki JZR #START LP1: SUB #48 ;odejmujemy kod ASCII STA $X ;cyfrę umieszczamy w zmiennej pomocniczej LDA $DANE ;liczbę mnożymy przez 10 MUL #10 ADD $X ;dodajemy cyfrę STA $DANE LDA $INOUT ;pobieramy kolejną cyfrę JZR #KONIEC ;jeśli koniec cyfr, kończymy JMP #LP1 ;w przeciwnym razie kontynuujemy KONIEC: END |
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe