Virtuális gép, Assembler, távirati stílusban [V. rész, bővítések]

Az utasításkészlet bővítése.

Nem csak az assember és a processzor forráskódja szimpla.  A VM utasításkészletének bővítése is  hihetetlenül egyszerűen, akár két-három sornyi kóddal (!) kivitelezhető. Legalábbis, a legtöbb esetben.  Nézzük például a SWAP utasítást.

Az utasítás, nevéhez méltóan az ACCU tartalmát cseréli fel a paraméterbeli memóriacímen lévő tartalommal. A SWAP mnemonikja legyen SWP, az OP-Kódja pedig  CD (change data). 

Ha ACCU = 10  és MEM[12] tartalma 6 akkor a SWP 12 utasítás végrehajtása után az
ACCU lesz 6 és a MEM[12] tartalma lesz egyenlő 10-zel.

Ehhez az assembler 096 és 097 sorai közé ezt:   'SWP' : MEM[IP]:= $CD;

A uVM kódjába pedig ezt a sort kell beszúrni a 053 és a 054 sorok közé:

   $CD: {SWP} Begin TMP:= ACCU;  ACCU := MEM[MEM[IP+1]];  MEM[MEM[IP+1]]:= TMP; inc(IP,2); end;

továbbá a 038 és 039 sorok közé még ezt:

 Var TMP : Byte;   

S lám, készen is vagyunk.  Nifty??
De nézzünk mást. Valami még jobbat.      

Az INC utasítás és további lehetőségek

    Az INC (increment) utasítással például az ACCU regiszter tartalmát lehetne növelni. Hát építsük be.
    Az utasítások mellé, mint azt tudjuk, az assembler szinte már elvár egy paramétert. Ez a paraméter itt fölöslegesnek tűnik, mert az INC önmagában elég az ACCU eggyel való növeléséhez.

    Na de ha mi mégis paraméterezzük az INC-et és kihasználjuk a benne rejlő lehetőséget, akkor tehetünk olyat, hogy a paraméter tartalmától függő számmal inkrementálja az ACCU-t (és ne csak eggyel).
    Vagy azt is, hogy ha a paraméter nulla, akkor az ACCU-t, ha annál nagyobb szám, akkor viszont a MEM[PARAM] címen található értéket inkrementálja eggyel. 
Ugyanez megtehető a DEC (decrement) utasítás beépítésével is, ami csak annyiban különbözik az INC-től, hogy más a mnemonikja, na meg az OP-kódja és nem növel, hanem csökkent. 

Decimális, hexadecimális, bináris

Az assemblerünk jelenleg nem ért, csak decimálisul, tehát kizárólag a mindennapokban megszokott, tizes számrendszer számait képes feldolgozni. 
Na de a számítástechnikának ezen berkeiben a hexa kijelzés mellett nem ritkán a bevitel is tizenhatos számrendszerben történik.
Ahhoz viszont, hogy az assemblerünk képes legyen feldolgozni, egyáltalán, felismerni a tizenhatos számrendszer számait, némi bővítést kell végrehajtanunk. Nem kell félni, ez sem fog fájni.

Azt már tudjuk, hogy a Token-eket azok első karaktere (Token[1]) alapján szortírozza az assembler.  Ennek segítségével dönti el, hogy az utasítások melyike DAT és egyáltalán, melyik utasítás, vagy melyik szám. 

Az assemblernek ez  a képessége  jól jön akkor is, amikor nem a "DAT/nem DAT" reláción kell dolgozni, hanem egy hexadecimális számot kell tudni felismerni.
Az már említésre került, hogy a decimálistól eltérő számok más nembeliségét valahogy tudatni kell az assemblerrel, ezért is van a hex számok előtt a $, vagy a 0x prefixum (előtag), netán a h postfixum. 
A mi prefixumunk az lesz, amit választunk, de ebben az esetben érdemes a kialakult szokásokat követni.  Én a pascal stílusút használtam fel, tehát a $-t. 

Fontos!

Ha valaki számára valahogy elsikkadt volna: az új utasítások beintegrálása során arra legyünk figyelemmel, hogy új utasítás kulcsszava NEM KEZDŐDHET 'D' (vagy 'd') betűvel, mert az assembler azt használja a DAT-ok szelekciójához.  Az assembler egyébként case inszenzitív, tehát kis- és nagybetű között nincs különbségtétel. az add és az ADD, vagy aDD, netán Add ugyanazzal a jelentéssel bír. 
 

  (folyt. köv.)

Kényelmi megoldások

A kimenet, amit a VM generál, szimpla, de legalább jól áttekinthető. Ezt az egyszerűséget is meg lehet törni, a VM-et is sokkal használhatóbbá lehet tenni, szintén minimális energiabefektetéssel. 

A VM első komoly hiátusa, az, hogy a DUMP-ot kell olvasni és táblázatot használni az utasítások dekódolásához (tudjuk, nincs ebből olyan sok, na de mi van ha később több lesz? 16 vagy 20-24  akár), pedig mindezt egyszerűen gépesíthetnénk, egy beépített disassemblerrel. 
Megrémülni nem kell, a feladat könnyű. Annak inverziója, amit az assembler csinál a kulcsszavakkal. De mit is csinál? Hát a kulcsszót megfelelteti egy OP-kóddal. Most ennek ellenkezője szükséges. 

Nem ügy. Az egész disassembler cakk-pakk, ennyiből áll:
 

disassembler fggv.

    A disassemblálandó OP-kódra a memóriaterületen az OldIP (vagy ExIP) változó mutat. A függvény visszatérési értéke maga az utasítás kulcsszava. Meghívni a DrawScreen procedúrán belül kell, amikor az ACCU, IP, stb. elemeket iratjuk ki.
Ha ezt netán kiegészítjük az utasítástól jobbra lévő memóriahely (a paraméter) tartalmának dekódolásával (ami igazán nem nehéz), akkor a komplett forrást képesek leszünk rekonstruálni. Sorról sorra. Persze a kommentek nélkül.

    Még egy tipp: Nem csak az aktuális, végrehajtás alatt álló utasítást, de az eggyel korábbit, sőt, a soron következőt is érdemes dekódolni. Tudom, így, elsőre nehéz belátni de esetenként ez egy komoly segítség.

   A kimenet egy plain text file. Ezt fel lehet javítani ha az output plain text helyett mondjuk .html. A html szines, így küllemre szebb és grafikus lehetőségeket is rejt. Nem mellesleg, kezdők is elbírnak vele. 

    Meg lehet tenni azt is, hogy a kimenetre csak egy bizonyos ciklus lefutása után íródjon a VM tartalma, vagy akár triggerelni, feltételtől függővé is lehet tenni a kiírást.  Aki nem akar fájlba iratást, az átalakíthatja olyanra, vagy készíthet olyan verziót is.
    A log file mindenesetre kiváló eszköz a tanulni vágyóknak, hiszen a saját programjának minden lépését elemezheti, figyelheti annak kihatását, a memóriatartalom változását.

    A végtelen ciklus elleni védelem (CMAX) értéke is felülírható. A 16384 soknak tűnhet,  de egyáltalán nem nehéz olyan programot írni, ami ezt is kevesli. Az előző rész végén ismertetett szimpla counter program csak tizenötig számol, mégis, ez magában  mindjárt hetvennégy ciklust igényel.
Erre is legyünk figyelemmel.

=====================================================================

A bejegyzésekben szereplő forráskódokat a  freepascal nevű fejlesztőeszköz fordítójával lehet lefordítani. Ingyenes, multiplatform (Win/Linux/Mac/ARM) és sok előnye van. 

http://www.freepascal.org

A nyelv kis és nagybetű között nem tesz különbséget, kezdőknek nem kell include fájlokkal, klf könyvtárakkal vergődniük, nincs memóriaszemét sem. A pascal emellett egy szabálykövető, erősen típusos, szerintem jól olvasható nyelv és nem nehéz elsajátítani.
 

Hozzászólások

Továbbra is élvezettel olvasom. Igaz, ilyen VM-et írtam már régebben. Ott AND, OR, XOR, SHL, SHR, ADD, SUB, MUL, DIV, MOD, INCJL, DECJGE, JE, JNE, CALL, RET készlettel dolgoztam.
Egyúttal ha Rust-tal is akarsz ismerkedni, akkor itt van az alap futtatókörnyezeted, néhány apró hiba javítva benne + nem kizárt hogy valami más hiba is belevíve. :) Ez esetben szólj, javítom.

$ rustc -O uvmm.rs
$ ./uvmm 64byteprogifile

Apropó: assembly fordító lesz a következő. A   "cimke:" nagyon hiányzik az assembly fordítóból, hogy a JMP helyét ne kelljen kézzel kiszámolni. Ahogy a nevesített RAM rekesz is igen hasznos az alap fordítónál is.

Hát ez remek! Hadd köszönjem meg a munkádat. Nagyszerű, amit csinálsz. Örömet okoztál vele nekem és talán másokat is sikerül inspirálnod.
Az assembler portját előre is köszönöm, várom. :)

Említed a te VM-edet. Az enyém is túllélpett már az itt publikált szinten. Nem is 16 (ennyire hivatkoztam, tévesen) hanem 17 utasítása van már.
Hibák. Abban titokban bíztam, hogy  egy-két hiba marad benne, mert legalább pezseg a fórum, lesz mit megbeszélni, helyretenni. Meglep, hogy erre csak te reflektáltál.
Címke. Igen, be kell látnom, ez nagy hiányossága az egésznek. címkék még az enyémben sincsenek. Ehelyett inkább készítettem egy editort, ami kiszámolja a címeket.:)

Régen ezeket még papíron számolták az elődeink. Gondoltam, nem tesz rosszat, ha megéljük azt, amit ők nap mint nap érezni voltak kénytelenek. Nekem is okozott gondot a címek számolgatása, de megszoktam. A komment lehetősége emiatt került be. :) Viszont most, hogy említetted, lehet, hogy az érdeklődőket visszarettenti a címkék hiánya. Ez talán fontosabb is mint a hex/bin adatbevitel lehetősége.
Gondolati síkon megfogalmazódott bennem, hogy megcsinálom. @0: lenne a címkék formátuma, a középső karakter pedig 0-tól 9-ig számozódna. 10-nél több fölösleges, mert annyi el se nagyon férne ekkora memóriaterületen. De még átgondolom, lehet, hogy van ennél szebb, jobb lehetőség is.

Szerkesztve: 2021. 06. 29., k – 22:19

Esetleg egy pontosítás:


$CD {SWP}: Begin TMP:= ACCU;  ACCU := MEM[MEM[IP+1]];  MEM[MEM[IP+1]]:= TMP; end;
Szerkesztve: 2021. 06. 30., sze – 22:09

Na, beleapplikáltam a címke kezelést is. 

Hát, még nincs igazán kész, nem végleges,  de kb. ilyesmi lesz.  Egy ugrást mindenképpen végre kell hajtani (legalábbis egyelőre), a főprogram-részhez, ugyanis a cimkéket csak így tudja most még feldolgozni (a cimkékre csak akkor lehet hivatkozni, ha az assembler detektálta a létezésüket (ergo, ha már tud a címükről)).

Itt a forrás linkje :  https://ideone.com/agEx2i  (nem kell gépelni)

; label test
; ----------
DAT  2; 
DAT  5;
DAT 32;
JMP 30; ugrik a foprogramra és onnan kezdodik a vegrehajtas
#1:     az elso cimke
ADD  1; 
ADD  2;
STA  8;
ret; 
#2:     a masodik cimke
ADD  3;  
STA  6;
ret;
JMP #2;  ugras  az  elso cimkere
JMP #1;  ugras a masodik cimkere
JMP 63;  end of game

Még annyit, hogy a címke formátuma: # (azaz hashtag), egy szám 0-tól 9-ig bezárólag, majd egy kettőspont.  Pontosvesszőt LABEL sor nem tartalmazhat. 
A címkére hivatkozás ugyanez, csak kettőspont nélkül. (lásd feljebb)

Az ugrási cím miatt ez így csak 64 byte memóriával működik. 256 byte memó esetén hozzá kell adni az első ugrási címhez (ez most 30 (decimális)) még 48-at.
 

Itt a label test utolsó két ciklusának kimenete is, ellenőrzéshez:

 

    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

0  22 02 05 20 00 00 20 00 27 00 00 00 00 00 00 00 
1  EA 1E AD 01 AD 02 BA 08 FF AD 03 BA 06 FF EA 19 
2  EA 12 EA 3F 00 00 00 00 00 00 00 00 00 00 00 00 
3  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

   Accu: 27  Ip: 18  Instr: FF
---------------------------------------------------| 10
    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

0  24 02 05 20 00 00 20 00 27 00 00 00 00 00 00 00 
1  EA 1E AD 01 AD 02 BA 08 FF AD 03 BA 06 FF EA 19 
2  EA 12 EA 3F 00 00 00 00 00 00 00 00 00 00 00 00 
3  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

   Accu: 27  Ip: 22  Instr: EA
---------------------------------------------------| 11
Szerkesztve: 2021. 06. 30., sze – 08:27

Azért egyedi lett ez az assembler.
JMP + RET ... máshol CALL + RET,  a JMP-nél nem szokás elrakni semmiféle visszatérő értéket.

label:   ; és itt a kettőspont végződésre szokás szűrni.
Ehelyett ismét sajátos megoldás. :)

De ez végülis nem baj, amíg szabadidős játék. A probléma ott kezdődik, ha kinőne valami komolyabb belőle és eltér a megszokottól.
És ha már megszokott assembly: itt egy elterjedt formára példa, amit tovább gyúrhatsz, ezáltal elsajátíthatod az elterjedt varázslatokat belőle.

Ha az ember csinál valamit, akkor egy idő után nem csak ő hat arra a dologra, hanem az is visszahat rá. Gondolom ennek tudható be, hogy nekem most valahogy jobban tetszik a címszámítgatás a label-nél. Return eredetileg nem volt benne, a visszalépéshez is ugrani kellett, számítottan. Csak ezt a többi nehézséggel együtt már soknak tartottam. Az elsődleges cél az volt, hogy az utasításkészlet minél kisebb legyen. Fél éve említettem is, hogy a PDP-8 volt az etalon. A legelső szet csak hét tagot számlált [LDA, STA, ADD, JEQ, JLESS, AND, NOR] és első körben nem is akartam többet. De azt nyilván senki nem állt volna neki használni. Ráadásul itt arra is figyelemmel kell (legalábbis nekem kellett) lenni, hogy az oda-vissza jump 4 byte-ot igényel a max. 256-ból, ami luxus. A CALL két plusz utasítás, ami végső soron egy RET-tel (és eltárolt visszatérési címmel) kiegészített jump. Nyolc - nagyon esetleg tíz - utasítás és abból mindjárt három ugró meg egy RETURN?  Na nee..
Hát akkor miért is ne lehetne a JUMP és a JZERO, egy RET-tel kiegészítve a CALL helyett, legfeljebb, ha nem akar valaki visszatérni, akkor majd jó erősen kerüli a RET használatát és kész. A köv ugrásnál  felülíródik a stack? Mint a vihar!  Hát akkor mi a baj? Ja , hogy a konvenciók, a megszokás. Azokat is kialakította egyszer valaki, nem?  

Az is a konvenciók része, hogy apu, anyu lazán behazudta nekünk, hogy karácsonykor jön a jézuska, aztán a valóságban a fater sunnyogta be a fa alá a lomokat, amikor azt hitte, hogy éppen nem figyelünk. Pedig mi figyeltünk. Semmi mást nem figyeltünk aznap, csak azt a kurva fát.
Szóval, a jézuskás fedőtörténet egy ordenáré nagy kamu volt, ennek ellenére, én nem láttam még egy felnőttet sem, aki karácsonykor ott bőgött volna az ágya szélén, hogy jaj, nem jön a jézuska, és, hogy őt átverték, neki mocskos módon hazudtak a szülei. Pedig tényleg hazudtak neki.

Ehhez képest a JUMP és a JZERO esete nem hazugság, hanem mindössze annyi, hogy használatuk  kissé eltér a konvencionálistól.

label:   ; és itt a kettőspont végződésre szokás szűrni.
Ehelyett ismét sajátos megoldás. :)

Annyira sajátos, amennyire a már meglévő kód struktúrája megkivánta. 
Azt nem tudom, hogy a címkékre való hivatkozásoknál milyen kettőspontot vesznek figyelembe a fejlesztők.

De az alkotói szabadság kijár az alkotónak. :)

Itt épp a szabadság hiánya okán olyan a dolog, amilyen. Nem volt tervben a címke. Úgy, anélkül lett megfogalmazva az egész.

Az elsődleges szempont az érthetőség és a forráskód rövidsége, egyszerűsége.  Erre kell figyelemmel lenni a bővítés során is,  hogy ez a két szempont érvényesüljön.  Én erre törekszem.

Ennek az egésznek az a célja, hogy akiket érdekel, azok számára bemutassa egy processzor működését, a memória szerepét. Hogy aki akar, az kipróbálja rajta az összegzés tételét, a kiválasztás tételét, egy buborék sortot, hasonló, egyszerű dolgokat. Lássa, hogy az ő programja is fut. Emellé ott a forrás, hogy megismerhesse az efféle dolgok felépítését és ha tetszik neki, akkor játsszon vele, bővítgesse, változtassa meg a szerkezetét, kedve, igénye szerint.

Nem akarok párhuzamot vonni Tannenbaum professzor és személyem között, de ez annyiban hasonlít az ő termékére, a miinixre, hogy az is, operációs rendszernek nem felel meg, de tanulni annál inkább. Ez is valami hasonló. Legalábbis én így gondolom.  Nem célja konkurrálni se a Netwide assemblerrel sem a nagy virtuális gépekkel.

Nem akarja és nem is fogja sehova kinőni magát.  Marad az, ami. Így képes betölteni a funkcióját.

azok számára bemutassa egy processzor működését

Ehhez nem kerülöd el
   RAM - egyszerű CPU - ROM
hármasát, a ROM mehet kapcsólókkal is. A vonalakra LED-ek és a CLOCK is kapcsolóval léptetve.
(egyszerű CPU akár Verilog-ban megírva CPLD/FPGA-ra)

De kit érdekel ilyen szinten? Pedig így lehet megérteni. Kockás papír és kézzel léptetett műveletek által, miközben előre fejben kikalkulálod a LED-ek állapotát.

Megjegyzés: esetünkben 15 byte lehetne a RAM, és 48 byte a ROM. A maradék egy byte az I/O port, ha olvassuk input-port, ha írjuk, akkor output.

Olyasmiken is lehetne törpölni, hogy a VM képességeit korlátozzuk, hogy jobban hasonlítson a valódi CPU-ra, pl.: memóriát csak regiszteren keresztül lehessen elérni, továbbá a felhasználó számára látható regiszter(ek)en kívül csak két munkaregiszter van (mondjuk W és Z).

Ekkor a swap ilyesmi lenne (az opcode beolvasása után vagyunk, IP az opcode mögé mutat):

W := MEM[IP]
++IP
Z := MEM[W]
MEM[W] := A
A := Z