Virtuális gép, Assembler, távirati stílusban [I. rész, a VM]

Három közlendő:
- E sorok írója asperger szindrómában "szenved",

aminek az az egyik következménye, hogy a kommunikációs készsége botrányosan rossz. Ennek egyik velejárója, tünete, legalábbis a tapasztalatok szerint, hogy a szövegei alkalmasint nagyképűséget, felsőbbrendűséget, vagy diktatórikus hangnemet sugallnak. Erről valójában szó nincs, de akit ez a "stílus" mégis zavar, az inkább ne olvassa az alábbi sorokat, vagy ha mégis megteszi, kérem, a véleményét tartsa meg.       
- Akit tényleg a megismerés vágya hajt, annak azt ajánlom, hogy ne válogasson, az írás minden részét olvassa el, próbálja meg értelmezni, mert a dolog teljes megértéséhez, ha kezdő, valószinűleg csak így fog tudni eljutni.    
- Az alábbi cikk helyenként radikálisnak tűnő állításokat tesz, ami enyhébb fokban sokkolhat olyanokat, akik ezen a területen - amiről alább szó esik - járatlanok. Mentőre, orvosra  azért nem lesz szükség.

   Ebben a részben tisztába teszünk dolgokat, megtervezzük és felépítjük a mi kis virtuális gépünket,
de előtte megszerezzük az ehhez szükséges ismeretek minimumát.

   Kezdjünk is bele.
   A világ egyik legelsőbb processzora, az intel 4004-es még csak 2300 darab kósza tranzisztort foglalt magába. Az apple I és apple II számítógépek szíve, a 6502 CPU már 3500 tranzisztorból épült fel.
Manapság az i5, i7, i9 korában a tranzisztorok számának milliárdos a nagyságrendje ezekben a korszerű processzorokban. Legalábbis ezt hirdetik úton útfélen, erre hivatkoznak a szakcikkek is. Pedig, a valóság az, hogy bár egy CPU tényleg tartalmazhat sok százmilió, vagy ennél is több tranzisztort, ám ezek többségének az érdemi munkához, a számításokhoz nem sok köze van. A milliónyi tranzisztort jobbára a hatalmasra hízott cache memória igényli, nem a processzor.
   Egy flip-flop jellemzően hat tranzisztorból épül fel és ez mindössze egyetlen bit információ tárolását teszi lehetővé. Ebből a six pack-ból kell nyolc darab, egy árva byte-nyi memória kiépítéséhez. Ez mindjárt negyvennyolc darab tranzisztor és egy kByte-hoz [kibibyte] ezt ezerhuszonnéggyel kell megszorozni. Tehát, egy kByte tárolóterület kb. negyvenkilencezer, 100 kByte viszont már mintegy fél millió db tranzisztorból épül fel, és akkor még hol vagyunk a MegaByte-októl?
   Az kétségtelen, hogy manapság, számításigényes környezetbe cache memória nélkül nem érdemes processzort delegálni, mivel a cache nagyban növeli egy CPU hatékonyságát, de ne feledjük, maga a CPU beéri a milliárdnyi tranzisztornál sokkal kevesebbel is.
 

Gates, gates, gates

   A szakmabelieken kívül kevesek számára ismert, hogy a legbonyolultabb processzor is mindössze három logikai kapu ilyen-olyan kombinációjából tevődik össze. Igen. Csak és kizárólag három logikai alapkapu létezik, ezek:  AND, OR, NOT, magyarul az ÉS, a VAGY és a NEM kapuk. Ez a három alapkapu is annyira egyszerű, hogy ennél egyszerűbb már nem is lehetne. Itt egy kép alább, amely igazolja, hogy nincs ebben semmiféle túlzás.    

and, or, not gates

   Talán többek számára feltűnt, hogy ezek a kapuk lényegében nem egyebek, mint sorosan vagy párhuzamosan kötött kapcsolók. Igazuk van. A tranzisztorok is afféle kapcsolók. Az ilyen logikai kapukat tranzisztorokból szokták megvalósítani. Innen ered a TTL betűszó is. Tranzisztor, Tranzisztor Logika.

A logikai kapuk kapcsolói két állást vehetnek fel. Az egyik a kikapcsolt állapot. Ezt nevezzük el nullának. A másik a bekapcsolt állapot, ez meg legyen az egyes.

A kapuknak a kapcsolók a bemeneti pontjai és az én rajzomon a kimeneti pontok állapotát, ami szintén vagy nulla, vagy egyes lehet, a LED-ek reprezentálják. Ha a LED világít, akkor a kimenet egyes, ha nem, akkor nulla. 

A kapuk közül az inverter (ez a NEM kapu) megelégszik egy bemenettel. Ami oda érkezik, annak ellenkezője jelenik meg a kimenetén. Így lesz a nullából egyes, az egyesből meg nulla.

De a másik két kapunak legalább két bemenet kell, hogy kimenetet tudjanak szolgáltatni.

   Ezeknek a  kapcsolóknak - vagy inkább kapuknak - a neve a működésükből fakad. Az ÉS kapu azért ÉS, mert 'a' kapcsoló ÉS 'b' kapcsoló egyidejűleg bekapcsolt állapota hozza meg a kivánt eredményt, a világító LED-et, a zárt áramkört.
A VAGY kapunál már VAGY az 'a' VAGY a 'b' kapcsolt állapota is elég ugyanehhez. A NEM kapu (ez az inverter) is magáért "beszél".
Tehát mégegyszer, ilyen kapukból, vagy ezek különféle kombinációjából áll egy CPU.

Na de miből áll egy program?

   A számítógépes programok annyiban hasonlítanak a logikai alapkapukra, hogy esetükben is éppen három összetevő, három komponens valamiféle variációjáról beszélhetünk.
Egy számítógépes program az adatokon kívül az alábbiakból áll: szekvencia, szelekció, iteráció.
A szekvencia gyakorlatilag bármiféle utasítások hosszabb-rövidebb sora.
A szelekció az, amikor a program válaszút elé érkezik és döntést kell hoznia, ami hatással van a későbbi történésekre.
Az iteráció* valaminek - általában egy bizonyos kódrésznek - az ismételt végrehajtását jelenti. * van az iteráció szónak egy másik jelentése is. Ez most nem az az eset.
 

   Kevesen tudják, hogy a matematika, ami a bonyolultsága, érthetetlensége okán sok-sok diák rémálma, lényegében összeadás. Igen, így van. Nincs szorzás, nincs osztás, nincs négy alapművelet, csak egy. Ez pedig az összeadás. Minden, még a legbonyolultabb matematikai feladat is visszavezethető összeadások - nyilván hosszú - sorára.
Háromszor négy? Ne szórakozzunk már. Az négy, meg négy, meg négy. És mégis, milyen szinű osztás? Tizenkettőből négy, aztán megint négy és megint. Eredmény három, a maradék meg nulla. A szorzás, eleve, összeadások sorozata, azok hosszabb-rövidebb szekvenciája. Az osztás pedig kivonások sorára vezethető vissza. A kivonás lehetne A másik művelet aminek létjogosultsága van, már ha nem számítógépről lenne szó. Ugyanis, amikor megalkották a számítógépeket - legalábbis a maiakat - akkor arra törekedtek, hogy azok a lehető legkevésbé legyenek bonyolultak, mert ha valami minél egyszerűbb, annál olcsóbb is egyben. A modern, digitális számítógépeket tehát úgy tervezték meg, hogy ne legyen bennük szorzás, osztás, de még kivonás sem, csak összeadás.
Szóval. Ezt tudja az a nyomorult gép. Csak összeadni. Ezt viszont szédítően gyorsan. Ez a gép ereje. A hülyeséget sebességgel kompenzálja. Persze egy adott szám másik számmal való csökkentése éppen olyan fontos, mint két szám összeadása, de sajnos a kivonás a három logikai alap-kapuval (és, vagy, nem) már bajosabban lenne kivitelezhető, így a leleményes mérnökök a kivonást is összeadással pótolták.
   Hogyan lehetséges ez?
   Ha a kisebbítendőhöz (5) hozzáadjuk egy szám (3) negatívját (-3), akkor, bár a művelet összeadás, az eredményt (2) elnézve úgy tűnik, mintha kivonás történt volna. Ugyanez történik, csak egy kicsit csavarosabb formában a digitális, bináris rendszerekben. Ott a folyamat a következő:
   Veszik a kivonandó szám bináris formájának egyes komplemensét (ez úgy megy, hogy a szám minden bitjét áthajtják egy inverteren, ami egy NOT kapu), így ami nulla volt, abból egyes, ami egyes volt, abból meg nulla lesz. Majd ebből, mint eredményből képezik a szám kettes komplemensét (az egyes komplemenshez hanyagul csak hozzáadnak egyet) és végül ezt a kettes komplemens nevű számot adják hozzá a kisebbítendőhöz.
A kettes komplemens képzés valahogy így néz ki: Bináris forma, inverzió és plusz egy.
 

komplemens

   Kicsit visszatérünk a kapukhoz. Azt említettem, hogy három alapkapu létezik. Ezeket azonban már tupírozták, variálták, sakkozgattak velük eleget az elmúlt évtizedek alatt, így megszületett ezek újabb csoportja, mint például a NAND, a NOR, vagy az XOR kapu is. Na, ez utóbbi egy érdekes kis darab. Mindjárt kiderül, hogy miért.
   Az AND kapunak ahogy minden kapunak, van egy úgynevezett igazságtáblázata. Ez az AND esetében a következő:
Ha az 'a' kapcsoló nulla és 'b' kapcsoló is nulla (ki van kapcsolva), akkor az eredmény (ez a LED) is nulla lesz. A LED nem fog világítani.
Ha az 'a' kapcsoló egyes (bekapcsolt állapotban van) és a 'b' kapcsoló is egyes, akkor az eredmény is egyes, tehát a LED világítani fog.
Ha a két kapcsoló közül az egyik, bármelyik nulla, akkor az eremény is mindig nulla lesz.
   Ezeket az alapigazságokat táblázatba lehet foglalni, ahol is a bemenetet a kapcsolók jelentik, a kmenetet (Out) pedig a rajzon jelzett LED.

AND kapu

   Ugyanez eljátszható a VAGY kapuval és természetesen minden más logikai kapuval is. Jut eszembe. Száraz ez az anyagrész? Mint a sivatag homokja. De nagyon értékes!
   A többi kapu boncolgatásába nem állok bele, akit érdekel, az talál hozzá forrást eleget. Egyet viszont egzámen alá kell vonnunk. Ez az a bizonyos XOR kapu.
   Ennek igazságtáblája itt van alább és ez már kellően izgalmassá is teszi ezt a területet. Nézzük csak:

xor kapu

  

   Túl azon, hogy ez a kapu elég költségkímélő, hiszen négy tranzisztor elég a megvalósításához, mit is látunk itt?  
   Azt, hogy ez a kapu, egy valóságos aranykapu, ami már-már megfelel az összeadás művelet elvégzésére! Figyeljük csak az igazságtábláját!
   Egy plusz nulla az egy.
   Nulla plusz egy szintén egy, hiszen az összeadás tagjai felcserélhetőek.
   A nulla plusz nulla is hozza az elvártakat.
   Egyedül az egy plusz eggyel van némi gond. Az sem túlságosan komoly, hiszen a kettes számrendszerben is létezik átvitel, ahogy a tizes számrendszerben. Már pedig a kettes számrendszerben az "egy plusz egy" az valóban nulla lesz, de átvitel is generálódik, tehát az egy plusz egy az bizony 10. Persze bináris 10, nem decimális. Csak hát, ebben az esetben az átvitelbit (carry) nem elpárolgott, nem semmivé lett, hanem még csak meg sem született. Na de ezen segít egy másik kapu (az AND) és mindjárt létesült is egy összeadónk. Ami így mutat:

félösszeadó

 

   Az igazság persze az, hogy ez nem egy összeadó, csak egy fél.
Miért? Mert azokat az összeadókat, amik ugyan képesek átvitel bitet generálni de maguk nem tudnak ilyen bitet befogadni és feldolgozni, félösszeadóknak hívjuk. Mindenesetre, már csak egy lépésre vagyunk a full addertől, azaz, a teljes összeadótól. Ha ez megvan, akkor olyan bitszélességű számítógépet építhetünk, amekkorát csak akarunk.  
 

   Na de, kezdjük végre el építeni a virtuális gépünket.
   Ehhez elsődleg' meg kell határoznunk azt az utasításkészletet, amit a gépünk majd végre is tud hajtani. Amint az a fentiekből is kiderül, lényegében egy összeadó-gépet kell csinálnunk. Persze nem akármilyet! Egy programozható összeadó-gépet. A legelső művelet, amit tudni fog, az addíció, az összeadás. Ez angolul az ADD. Tudnia kell a gépnek adatokat mozgatni, ezért kelleni fog egy STORE (tárol) utasítás és emellé egy LOAD (betölt) is. A gépünknek, ha már programozni akarjuk, meg kell felelnie annak a követelménynek, ami a programokról szól, tehát a szekvencia mellett tudnia kell szelekciót és iterációt végrehajtani. Na de mi kell ehhez? Mi ennek az alapja? Hát leginkább annak képessége, hogy két számot össze tudjon hasonlítani, ezért igen, *COMPARE utasítás is lesz. Compare = összehasonlít. Ehhez jön még annak lehetősége, hogy a programunk futását kivánság szerint eltéríthessük, ezért JUMP utasítás is lesz. Na de ebből kell ám még egy, ugyanis az a szép, ha van olyan ugró utasításunk is (ez lesz a JZERO), aminek bekövetkezése nem direkt, hanem valamilyen feltételtől is függ. Nálunk ez a feltétel a nulla lesz, azaz, ha a gépünk regiszterében a JZERO utasítás (JUMP IF ZERO) végrehajtása előtt nulla van, csak akkor fog a mi JZERO utasításunk végrehajtódni. Így tehát van már feltételes és feltételtől nem függő ugró utasításunk is. Nézzük a listát, mi van eddig? ADD, STORE, LOAD, COMPARE, JUMP, JZERO.
   Ezzel már elég jól el is lennénk, de kell még ide valami. Valami nagyon fontos.
   Régen úgy írták programjaikat a kőbaltás emberek, hogy az elkezdődött egy utasítással és az egész egy hosszú szekvencia lett. Utasítások egymás-utánisága. Azonban később rájöttek, hogy a gyakrabban ismételt kódrészeket jobb (és memória-takarékosabb) lenne nem újra és újra beírni, hanem inkább úgynevezett alprogramba, függvénybe, procedúrába vagy szubrutinba szervezni, aztán ha szükséges, ezeket a procedúrákat meghívni (JUMP). Azonban volt egy kis gond. Ha a program egy pontjáról (címről) csak úgy elugrunk egy másik pontra (címre), hogy egy procedúrát végrehajtassunk a géppel, utána illő lenne ugyanoda vissza is lépni a folytathatóság okán. Ehhez azonban ismerni kellene azt a pontot (címet) ahonnan a procedúrát meghívtuk. Mit lehet ilyenkor tenni? Hát azt, hogy az ugrás végrehajtása előtt egy fix helyen eltárolni a sorban következő pontot (címet) és amikor a procedúra végéhez érünk, akkor ezt a címet kiolvasva vissza tudunk térni oda, ahonnan a végrehajtást folytatni tudjuk. Na de a memóriában egymás után vannak az utasítások. Honnan fogjuk tudni, hogy mikor ér véget a procedúra? Hát onnan, hogy az is kap egy utasítást. Ez Lesz a RETURN (Return = visszatérés). Már-már szinte készen is vagyunk. Az utasítás-készletünk megvan, Mi kellhet még? Három dolog.
   Az egyik, hogy a gépünk processzorának legyen memóriája, mert anélkül nem lesz működőképes. Mint tudjuk, a memória tárolja a futtatandó programot és annak eredményeit is.
   A másik dolog, hogy a gépünk, ha egy műveletet elvégez, akkor annak eredményét valahol, legalább átmenetileg tárolnia kell. Ehhez szükségünk lesz egy regiszterre. Ez a regiszter nálunk az ACCUMULATOR vagy rövidebben ACCU névre fog hallgatni.
   A harmadik elengedhetetlen kellék egy másik regiszter lesz. Ennek az lesz a szerepe, hogy tudja, éppen hol tartunk a programunk végrehajtásában, tehát ez a regiszter az IP nevet kapja, ami angolul INSTRUCTION POINTER, magyarul utasítás mutató de legyen inkább utasítás számláló. Ez a regiszter a program futtatása előtt mindig egy adott értéket vesz fel, ami a futtatandó programunk első utasítására mutat a memóriában (erre majd később bővebben visszatérünk).
   Hogy most már valami képet is kapjunk a dologról, itt egy könnyed vázrajz a virtuális gépünk szerkezetéről:

a VM

    Ehhez az egyelőre elég szerény, sekélyes képhez egy kis magyarázat:
    A piros színnel jelzett négyzet egy regiszter, ez az ACCU. Aritmetikai vagy logikai műveletek végzése a későbbiekben MINDIG az ACCU tartalmának és az egyik memóriacím tartalmának felhasználásával történik és MINDIG az ACCU tárolja a művelet eredményét. 

    A zöld szinű pedig az utasítás számláló. Utóbbi, működés, futás közben mindig éppen arra a memóriarekeszre mutat, amelyik rekesz tartalma végrehajtás alatt van. Ha végzett az utasítás végrehajtásával, akkor ez a regiszter automatikusan inkrementálódik, növekszik. A következő utasításra lép, annak végrehajtása kezdődik el és így tovább, a program végéig.
Még valami. Ahhoz, hogy egy aritmetikai műveletet (összeadást, kivonást) végre tudjunk hajtani, szükség van két számra. Ezek egyike a memória valamelyik rekeszében, a másik pedig az ACCUMULATORBAN (ACCU-ban) foglal majd helyet.
Aki azt kérdezi, hogy miért nem mindkettő a memóriában, annak azt válaszolom, hogy azért, mert a művelet eredményét is tárolni kell valahol, az pedig nem lenne jó, ha egy memóriabeli értéket a gépünk akaratunk ellenére írna felül, hiszen lehet, hogy később is szükség lesz rá. 
    Már említettem, hogy a műveletek eredménye mindig az ACCU-ban fog megjelenni, tehát egy összeadás úgy néz ki, hogy adott memóriacímről az ACCU-ba töltjük az egyik számot, majd kiadjuk az ADD utasítást, ám annak paraméteréül megjelöljük azt a memóriacímet, aminek tartalmát az ACCU-hoz akarjuk adni. Tehát,az 5+3 művelet valahogy így néz majd ki.

A memória tartalma:

MEM[01] = 5
MEM[02] = 3
MEM[03] = 0

A program:

LOAD ACCU MEM[01]  betölti a mem[01] tartalmát az ACCU-ba
ADD MEM[02]   hozzáadja az ACCU-hoz a mem[02] tartalmát

ennek az eredményét (8), az ACCU fogja tartalmazni, amit a STORE utasítással kiírhatunk a memória valamelyik rekeszébe:

STORE ACCU MEM[03] persze ha akarjuk, felül is írhatjuk az eredeti számot, így:
STORE ACCU MEM[01]  de ez már a mi választásunk lesz, nem a gépé.

Harvard és von Neumann

   A PC-k Von Neumann elvű gépek, ami a dolog velejét tekintve annyit jelent, hogy az adat és a program memória fizikailag nem különül el egymástól. A gépnek tehát, mondjuk úgy, egyetlen összefüggő memóriaterülete van. Ám belső elkülönülés ebben az esetben is bekövetkezik. Nevezzük úgy, szegmentálódás. Minden programnak három ilyen szegmense van, legalább. Ezek a verem- az adat- és a kódszegmensek.

   A verem (stack) elsődlegesen arra szolgál, hogy ha egy procedúrát, szubrutint, függvényt, arankanénit meghív a főprogram (vagy másik procedúra), akkor ott tárolódjon az a visszatérési cím, amiről az előbb szó volt. De a verem tárolja azokat a változókat, paramétereket is, amiket a procedúra vagy függvény felhasznál és itt tárolódik függvények esetében a visszatérési érték is, A  procedúráknak ilyen alkatrészük, hogy visszatérési érték nincs. Ugye figyelünk és nem keverjük a visszatérési értéket a visszatérési címmel???

   A másik szegmens az ADATSZEGMENS. Ebben tárolódnak a program futása során felhasznált vagy - legalább felhasználható - konstansok, változók, literálok értékei. Magyarán, az adatok.
   A harmadik szegmens a KÓDSZEGMENS. Ez nem egyéb, mint a futtatandó program területe. Ide, ebbe a szegmensbe töltődik be a végrehajtásra kerülő kód.  

   A mi esetünkben a virtuális gép, mint azt igértem is, roppant egyszerű, sallangmentes lesz, hogy a megértést segítse. Erre az alapra persze lehet építeni, Nagyon komoly rendszerré lehet bővíteni, ráadásul, saját igény szerint.   
   A gépünk szervezése most a kezdetben, mindössze nyolc bitre korlátozódik és ez bizony kihat a VM fülétől a farkáig. A memória nyolc bites szervezésű lesz és a maximális mérete (címezhetősége) is nyolc bitre korlátozódik. Igen, ez mindössze 256 byte. De aggodalomra nincs ok, lesz ez még kevesebb is. Az ACCU regiszter is nyolc bites lesz és az IP regiszter sem lesz ennél nagyobb, már csak azért sem, mert ennél több fölösleges volna, hiszen nyolc bittel végigcímezhető a teljes memóriaterület.
   Akik meg akarnának most hökkenni, azok ne kapkodják el. Meglepően sok dolgot lehet művelni már ilyen kevés memóriával is. Lesz erre adva bizonyítás.
   Na de térjünk vissza a munkához, az építkezéshez, mert vészesen fogy az idő és már az a számfejtő is megszületett, aki ki fogja számolni a nyugdíjunkat.
   Szóval. Szóba került a három szegmens (verem, adat, kód), ami minden rendes programnak a sajátja. Ez így lesz nálunk is. Azt kell csak meghatároznunk, hogy melyik szegmens hova kerüljön. Tekintve, hogy a memória elég szűkös, így gazdálkodnunk kell vele, hogy minél több hely jusson a futtatandó programoknak. Ezért a memória szervezése úgy fog kinézni, hogy a teljes rendelkezésre álló memória első (!) egynegyedét foglaljuk le adatszegmensnek, a többi háromnegyed rész lesz a kódé. Verem-szegmensnek pedig az adat-szegmens legelső byte-ját fogjuk kinevezni.
   Lassan elérkezünk addig, hogy kódolhatjuk is le a VM-ünket. Egy dolog van már csak hátra, ez pedig az IO. Ugye, minden rendes gépnek van egy input lehetősége és legalább egy output is illő, hogy létezzen. Hát, nálunk ebből a kettőből  - egyelőre - csak az utóbbi lesz megvalósítva. Ennek az outputnak viszont szüksége lesz egy portra, ahonnan ki tudja olvasni az adatokat. Ezt a portot szintén az adat-szegmens rovására fogjuk megvalósítani, még pedig úgy, hogy a port az adatszegmens utolsó byte-ja lesz.

A VM teljes memóriatérképe:

VM memória

   Az egyszerűség kedvéért úgy döntöttem, hogy még jobban egyszerűsítek és a lehetséges 256 Byte-nyi memóriából mindössze annak negyedét, 64 byte-ot fogok felhasználni a demonstrációhoz, az itt olvasható magyarázatokhoz. Tehát, innentől kezdve 64 byte memóriás gépre vonatkozik minden további szöveg.  
   A VM-nek az inicializálás után minden memóriacíme (tehát a stack is, az IO port is) 00h-t fog tartalmazni. Az ACCUMULATOR regisztere szintén. Egy eltérés lesz csak, ez pedig az IP. Ez jelzi, hogy honnan kezdődik a program végrehajtás. Ez eleve nem lehet nulla, sőt, soha nem lehet nulla, hiszen ott van a stack!
   Az első 16 memóriarekeszt (ez a teljes 64-nek az egy negyede (256 esetén ugyanez 64 byte)) lefoglalja a stack, az IO port és a közöttük levő tizennégy darab, adatoknak fenntartott tárhely. Logikus tehát, hogy sorban a következő üres memóriacímtől érdemes betölteni a programot, a kódot. Mivel az első 16 cím a 00 és a 15 között van, a következő cím a tizenhatodik, ami hexadecimális formában éppen 10. Ez a 10h lesz tehát a kódszegmenünk kezdőcíme és ez lesz az IP regiszter tartalma is, legalábbis reset, vagy program betöltés után, közvetlenül a futtatás előtt.
   Talán az is sokak számára nyilvánvaló már, hogy a megírandó programjainkban a szubrutinokat, procedúrákat, függvényeket nem ágyazhatjuk egymásba, hiszen ahhoz ez a picike, egy byte-os stack - most még - nem elég. A programjainkat tehát úgy fogjuk szervezni, hogy minden szubrutin hívás után, jó kisfiúk módjára, szépen visszatérünk a főprogramba és onnan hívjuk meg a következő szubrutinunkat (ha egyáltalán szükség lesz erre). Máskülönben a visszatérési értékünket a második hívás csúnyán felülírná, ami nem tenne jót a közérzetünknek.

   Erre a futamra rá kell pihenni. Angolul úgy szokták írni, hogy  END of PART ONE, ami annyit tesz, hogy vége az első résznek.

   A holnapi napon folytatjuk, de akkor már úgy nyitok, hogy DEMÓ-zni fogom a VM-et, annak működését, majd egyenesen belevágunk a dolog közepébe, azaz, kódolni fogunk serényen.

Hozzászólások

Szerkesztve: 2021. 06. 22., k – 16:11

Tetszik, de nekem nincsenek képek. (Tipp: mivel http-vel jönnének https helyett.)

A szakmabelieken kívül kevesek számára ismert, hogy a legbonyolultabb processzor is mindössze három logikai kapu ilyen-olyan kombinációjából tevődik össze. Igen. Csak és kizárólag három logikai alapkapu létezik, ezek:  AND, OR, NOT, magyarul az ÉS, a VAGY és a NEM kapuk.  

 

Emlékszem, anno a 80as évek végén milyen megvilágosító élmény volt relékből megépíteni ezeket a kapukat elektrotechnika gyakorlaton. Közben persze már nyúztuk a C64-et, és akkor állt össze, hogy ott van előttem nagyban, ami piciben a gépben. 

Szerkesztve: 2021. 06. 22., k – 18:47

Nagyon jó bevezető.

Kettő dologgal egészítem ki: Harvard architektúra előnye, hogy a ROM olcsóbban volt gyártható a RAM-nál.
   Ferritgyűrűs ROM: https://youtu.be/P12r8DKHsak?t=39   átvezették (1) vagy nem (0). A gyűrükön felfűzött "sense" vonal érzékelte a bit állapotot. Egy ferritgyűrűt tipikusan 20 feletti ROM bitre használtak fel.
   Félvezetős ROM: "fuse" megoldás, kiégették a felesleges kapcsolatokat a dióda mátrixban. Ez is sokkal egyszerűbb volt, mint a többtranzisztoros RAM cella. RAM-ból így kevés kellett.

A Neumann elv előnye, hogy bármikor újratölthető szoftverű számítógépet kapunk. Ellenben itt a drágább RAM-ban van tárolva a szoftver is.

További érdekesség: a hardver stack olyan kicsi volt, hogy gyakran csak visszatérési értékre (IRQ esetén flag + visszatérési érték) használták, a regisztereket szoftver stack-be mentették. Illetve ha nem volt többszörös hívás, akkor dedikált RAM-ba mehetett. Tehát általában 2 stack volt implementálva, egyik hardver támogatással a másik szoftveresen.
Sőt volt ahol 1 mélységű register stack állt csak rendelkezésre és szoftverből valósították meg a stack funkciót is, ha több mélységben volt függvényhívás.

Apró kötekedés: az "ACCU" szót ki is lehetne hagyni a "LOAD" és "STORE" utasításokból, mint ahogy az "ADD"-ban sincs benne.

Jogosnak érzem a felvetésedet, legalábbis a dolognak ebben a félkész szakaszában.

Kihagyni bűn lenne, de össze lesz vonva.
A jelenlegi szószátyár forma az utasítások és főleg a példa terén még deduktív, magyarázó célzatú. Később minden utasítás pontosan három karakteres lesz (mivel ez is jelentősen egyszerűsíti az assembler szerkezetét), pl. a store-ból STA, a load-ból LDA lesz. Ez azért fontos, mert úgy vélem, az utasítás amennyire lehet, nevében tükrözze is azt amit csinál.
Tekintve, hogy adat mozgatására csak indirekt úton, az ACCU felhasználásával van (pontosabban lesz) lehetőség, így én helyénvalónak érzem a mnemonikokban való utalást az accu-ra.

 

Szívből gratulálok és köszönöm, szuper írás, csak így tovább!

Az a NEM kapu nem jó úgy. A kimenetet szimbolizáló fényforrást az ellenállás másik végére kellene kötni.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Mondom, hogy nem jó! Attól, hogy egy fényforrással párhuzamosan bekapcsolsz egy fogyasztót, nem fog kialudni a fényforrásod. Nálad sötét lesz a lakásban, ha bekapcsolod a vasalót?

Van az a tápfeszültség. Azzal jöjjön sorba az ellenállás. Az ellenállás tápra nem kapcsolódó vége és a táp negatív sarka közé jöhet a kapcsoló. A kapcsolóval párhuzamosan a fényforrás. Ez működni fog, és nem zárod rövidre a tápot.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

A blogot, az ismeretterjesztést jónak tartom. Néhány dolgot másképp csináltam volna. A kombinációs hálózatokról beszélsz, de arról nem, hogyan kerül tárolásra egy bit. Írsz 6 db tranzisztorról, flip-flop-ról, de egy statikus RS tárat jó lett volna lerajzolni, majd a latch-ek után meg kellene említeni minimálisan az élvezérelt D-tárat. Lehetne még JK, T is akár, de nem muszáj.

Aztán, ha van kombinációs hálózatunk és tárolónk, akkor tud lenni szekvenciális hálózatunk is. Nagyon nyersen fogalmazva egy CPU tulajdonképpen egy szekvenciális hálózat. Lehet beszélni ezek tervezéséről, állapot diagramokról. Ha nagyon ráérsz, statikus és dinamikus hazard, illetve milyen tervezési módszerekkel kerülhetők ezek el. Ha szekvenciális hálózat, akkor setup time, hold time, propagation delay, slew rate - fall time, rise time - maximális üzemi frekvencia. Apropó, egyáltalán minek kell az órajel, ha csak a baj van vele?

Persze az is kérdés, inkább software-es, vagy inkább hardware-es megközelítéssel dolgozod fel a témát.

De egyébként tetszik a laza műfaja, olykor pontatlan megfogalmazások ellenére is. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Jó amit csinálsz, ezt írtam is. Nem trollkodásnak szántam a kritikát, és nem is azért, hogy elrontsam a napod, hanem azért, hogy amit csinálsz, annak javuljon a minősége. Azért nem kezdek bele, mert egyrészt már csinálod, így kivenném a kezedből, ami nem volna korrekt, másrészt nincs annyi szabadidőm, de kedvem sem, hogy egy olyan szakkönyvet megírjak, amelyből gondolom, annyi van már, mint csillag az égen, de rutinom sincs benne.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Attól, hogy egy fényforrással párhuzamosan bekapcsolsz egy fogyasztót, nem fog kialudni a fényforrásod.

Nézd. Ez demonstrációs célzatú áramkör, amelkyben a hangsúly egy logikai kapun van. De azon túl, a hasonlatod sem áll, mert ez egy törpefeszültségű egyenáramú kör, amelyben a diódának van egy nyitófeszültsége, az ellenállásnak meg egy terhelése az általad nem ismert kapocsfeszültségű áramforrásra. 

Viccen kívül, én szinte mindig örülök, ha felhívják a figyelmemet a hibáimra, de ebben a kontextusban én ezt egyáltalán nem érzem annak. 

Én nem feszülök be. Neked kéne megértened, hogy mi az a szimbolizmus. 

Ez vajon mennyivel lett volna jobb?

https://seminarprojecttopics.com/wp-content/uploads/2018/01/not-gate-ex…

Inkább a tévedéseimről írnál, amiket említettél, hogy azokat javíthassam. 

Induljunk abból ki, amit linkeltél! A táp és a kapcsoló közé tedd a felső vízszintes vezetékbe az ellenállást, és úgy jó is lesz.

Értem, hogy amit linkeltél, az is borzalmas, de ez ne bátorítson újabb szörnyűségek elkövetésére. ;)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Induljunk ki inkább abból, hogy adott két db, egyenként 1V2 névleges feszültségű AAA elem sorba kötve, egy kisáramú kapcsoló, egy darab 100 K-s, 1 Wattos ellenállás és egy db piros szinű LED, beépített ellenállással. Ezt a rajzom szerint kötöd, majd a kapcsolót zárod és szólsz, ha netán mégis tovább világítana az a led, Oké?

A legnagyobb tisztelettel arra kérlek, inkább software-es megközelítésből tárgyald a kérdést.

Egyfelől LED-et nem hajtunk feszültséggenerátorosan, mert a LED i(u) karakterisztikája meredek. LED-et mindig áramgenerátorosan hajtunk. Erre persze egy ellenállás is elég lehet, ha a feltételek ezt megengedik.

Másfelől a 100 kΩ gyakorlatilag semmilyen változást nem fog okozni. Mekkora az általad vázolt 1.2 V-os akkumulátor belső ellenállása? Legyen mondjuk 50 mΩ. Akkor a két akksi sorban 0.1 Ω.

2.4 V * 100 kΩ / (100 kΩ + 0.1 Ω) = 2.3999976 V, tehát csökkent a tápfeszültséged 2.4 µV-tal.

Valójában a számításnál csaltam, mert ha a LED terheli az akkukat, akkor a LED adott munkapontban lévő dinamikus belső ellenállása párhuzamosan kapcsolódik az akkuk belső ellnállásával, így a 0.1 Ω picit, bár nem sokkal még  kisebb lesz.

Az 1 W-os 100 kΩ cseppet túlzás, hiszen (2.4 V) ^ 2 / 100 kΩ = 57.6 µW

Bár nyilván baj nincs vele, csak nagy.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Eddig tetszik, jo sorozat lesz!

Ha jol gondolom, itt inkabb a software-es resze lesz erdekes, es nem a hardware-es. Szoval az elrontott NOT nem kap sok szerepet. Amugy a kapuknal azt meg megjegyeznem, hogy NAND (vagy NOR) kapukkal barmi megvalosithato.

When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin

(Kieg: házi feladatként eltöprenghetünk azon, hogy hányféle kétbemenetű egykimenetű logikai kapu (függvény) lehetséges, ebből hány olyan van, ami valóban függvénye mindkét bemenetének (pl f(A,B)=#B nem ilyen (itt a # az invertálás jele)), és azokból hány olyan van, amivel minden mást meg lehet valósítani

Én ezeket a kapukat nem értem, hogy mi miből mit csinál, és hogyan. Mi közük van az azonos nevű logikai függvényekhez, és a processzorokhoz?

Ha jól értem, akkor az input az a kapcsolóállás (valami másik áramkörben a "lámpa" helyett itt kapcsoló van), és az output a "lámpa", ami szintén kapcsolóállás egy másik áramkörben? De akkor a NEM kapuban mi szükség van egy saját áramforrásra (meg egy fogyasztóra), amikor az csak a kapcsolóállást negálja?

Meg egyébként.. A Boole hálózatokat ismerem, kis körök meg négyzetek alkotnak egy szintezett gráfot, halad a sok nullás meg egyes felfelé, szépen ütemre.. Ha jól értem, akkor minden kis kapunak van egy saját áramforrása?

Minden kapunak kell, hogy legyen áramforrása, csak nem kötelező nekik "saját". Itt a rajzokon azért van "saját", mert egy-egy kapu van csak bemutatva, azok meg valahonnan kell, hogy táplálást kapjanak, másképpen nem működőképesek.

A logikai kapuk kapcsolói két állást vehetnek fel. Az egyik a kikapcsolt állapot. Ezt most nevezzük el nullának. A másik a bekapcsolt állapot, ez meg legyen az egyes.

A kapuknak a kapcsolók a bemeneti pontjai és az én rajzomon a kimeneti pontok állapotát, ami szintén vagy nulla, vagy egyes lehet, a LED-ek reprezentálják. Ha a LED világít, akkor a kimenet egyes, ha nem, akkor nulla. 

A kapuk közül az inverter (ez a nem kapu) megelégszik egy bemenettel. Ami oda érkezik, annak ellenkezője jelenik meg a kimenetén. Így lesz a nullából egyes, az egyesből meg nulla.

De a másik két kapunak legalább két bemenet kell, hogy kimenetet tudjanak szolgáltatni.

Lehet olyan kapukat is építeni, amelyeknek kettőnél több bemenetük van, de ez  most itt nem lényeges.
 

De.. egy két elemű halmazból (kapcsolóállás) képez egy másik két elemű halmazba (lámpa be van-e kapcsolva). Ez nem lehet a logikai NEM, vagy invertálás...  Az invertálásnak, mint műveletnek csak akkor lenne értelme, ha az alaphalmaz és a képhalmaz ugyanaz.

szerk: kell még egy ilyen megfeleltetés, és akkor így talán jó

                   input     output        
0 kapcsoló nyitott lámpa nem világít
1 kapcsoló zárt lámpa világít

https://www.electronics-tutorials.ws/wp-content/uploads/2018/05/boolean…

És ugyanez a megfeleltetés van a többi kapunál is végig

Ezek a kapuk az azonos nevű függvények fizikai megvalósításai. Nagyon okosan felhozta itt valaki, hogy régen, gyakorlaton megépítette ezeket a kapukat relékből. Hát ez is ilyesmi. A processzorokban csupa ilyen kapu van. Ezekből épülnek ugyanis fel.

A gyakorlati jelentőségük hatalmas.
A számításokat ilyen kapuk végzik. Lényegében mindent, amit a számítógéped csinál, ilyen kapuk állítanak elő. Ha egy rajz megjelenik a monitorodon, vagy ha elmentesz egy megírt levelet, esetleg játszol, minden esetben ilyen kapuk dolgoznak. Ezek állítják elő a képet, a hangot is.

 
 

és az output a "lámpa", ami szintén kapcsolóállás egy másik áramkörben?

Igen, jól látod, a LED egy kimenet, ami egy másik kapu bemenete is lehetne, de itt a kapu funkciójának bemutatása volt a cél.

 

De akkor a NEM kapuban mi szükség van egy saját áramforrásra (meg egy fogyasztóra), amikor az csak a kapcsolóállást negálja?

A NEM kapunál azt kellett világossá tenni, demonstrálni, hogy a bemenet (kapcsoló) záródásával a kimenet (LED) nem be, hanem ki-kapcsol. A kapcsoló nyitásánál pedig ellenkező történik, a LED világítani kezd. 

Ez azt jeleni, hogy amikor a bemenet nulla, akkor a kimenet egy és amikor a bemenet egy, akkor jelenik meg a kimeneten nulla. Ezért inverter a neve.

Én valamit nem értek az összeadó/kivonó kapcsán. Ugye írod, hogy

sajnos a kivonás a három logikai alap-kapuval (és, vagy, nem) már bajosabban lenne kivitelezhető

Kérdem én, hogy mennyivel bajosabb és miért bajosabb? A 3-bites összeadó (egyik bemenet (A), másik bemenet (B), előző átviteli bemenet (C)) igazságtáblája a következő:

A|B|X|Y|C
-+-+-+-+-
0|0|0|0|0
-+-+-+-+-
0|0|1|1|0
-+-+-+-+-
0|1|0|1|0
-+-+-+-+-
0|1|1|0|1
-+-+-+-+-
1|0|0|1|0
-+-+-+-+-
1|0|1|0|1
-+-+-+-+-
1|1|0|0|1
-+-+-+-+-
1|1|1|1|1

Látszik belőle, hogy az eredmény (Y) akkor magas, ha páratlan számú bemenet magas, tehát elég végig XOR-olni őket egymással (A ^ B ^ C), illetve, hogy átvitel akkor keletkezik, ha legalább kettő magas, azaz két-két-két AND kapcsolat össze OR-olva (A & B) | (A & C) | (B & C), de ebből a három műveletből kettőt össze lehet vonni úgy, hogy a közös elemük AND-elve a két különböző elem XOR-jával (((A ^ B) & C)), mert ugye VAGY az egyik, VAGY a másik magas (ez itt kizáró vagy), ÉS a harmadik is és így egy OR kaput megspórolunk.

Azaz képletben:

Y = (A ^ B) ^ C
X = (A & B) | ((A ^ B) & C)

Namármost, mivel az A ^ B mindkét képletben szerepel, így még egy XOR kaput megspórolunk, valahogy így:

              *=======*
A ========+===|       |           *=======*
          |   |   ^   |=+=========|       |
B =+======)===|       | |         |   ^   |=================== Y
   |      |   *=======* |       *=|       |
   |      |             |       | *=======*
   |      |             |       |
   |      |             |       |
C =)======)=============)=======+
   |      |             |       |
   |      |             |       |
   |      |             |       |
   |      |             |       |
   |      |             |       |
   |      |             |       | *=======* 
   |      |             |       *=|       |     *=======* 
   |      |             |         |   &   |=====|       |
   |      |             *=========|       |     |   |   |===== X
   |      |   *=======*           *=======*  *==|       |
   |      *===|       |                      |  *=======*
   |          |   &   |======================*
   *==========|       |
              *=======*

(Az ASCII-"art"-ért én kívánok elnézést, de képeket nem szabad beszúrni.)

A 3-bites kivonó (identikus A, B, C bemenetekkel) a következő igazságtáblával bír:

A|B|C|Y|X
-+-+-+-+-
0|0|0|0|0
-+-+-+-+-
0|0|1|1|1
-+-+-+-+-
0|1|0|1|1
-+-+-+-+-
0|1|1|0|1
-+-+-+-+-
1|0|0|1|0
-+-+-+-+-
1|0|1|0|0
-+-+-+-+-
1|1|0|0|0
-+-+-+-+-
1|1|1|1|1

Ebből rögtön kiviláglik, hogy az eredmény (Y) pontosan ugyanúgy jön létre, mint az összeadónál. Az átvitel már kissé bonyolultabb; az úgy jön ki, hogy ha 0-ból vonunk ki valahol egyet, azaz alacsony A-ból magas B-t, vagy C-t vonunk ki, (azaz AND-eljük vele), vagy alacsony B-ből vonunk ki magas C-t, úgy, hogy előtte nem történt kivonás, mert A is alacsony volt ((!A & B) | (!A & C) | (!A & !B & C)), de ebből az előző átviteli rész kiegyszerűsíthető úgy, hogy nem kell külön megvizsgálni az első bemenetre, ill. a másodikra, hanem csak az első két bemenet egyezését (invertált kizáró vagy kapcsolat) és az előző átvitel magas állapotát nézzük meg ((!(A ^ B) & C)).

Azaz képletben:

Y = A ^ B ^ C
X = (!A & B) | (!(A ^ B) & C)

A két képlet amit kaptunk, kvázi ugyanazt a kapcsolást eredményezi, annyi különbséggel, hogy a kivonónál az átvitelben A-t, valamint A és B különbségét (értsd: nem egyezőségét) invertálni kell:

              *=======*
A ========+===|       |           *=======*
          |   |   ^   |=+=========|       |
B =+======)===|       | |         |   ^   |=================== Y
   |      |   *=======* |       *=|       |
   |      |             |       | *=======*
   |      |             |       |
   |      |             |       |
C =)======)=============)=======+
   |      |             |       |
   |      |             |       |
   |  *=======*     *=======*   |
   |  |       |     |       |   |
   |  |   !   |     |   !   |   |
   |  |       |     |       |   | *=======* 
   |  *=======*     *=======*   *=|       |     *=======* 
   |      |             |         |   &   |=====|       |
   |      |             *=========|       |     |   |   |===== X
   |      |   *=======*           *=======*  *==|       |
   |      *===|       |                      |  *=======*
   |          |   &   |======================*
   *==========|       |
              *=======*

Miért lett volna ez bajosabban kivitelezhető? Csak két kapuval van benne több; ha soros a kivonó az ALU-ban, akkor összesen ennyi, ha párhuzamos, akkor annyiszor kettő, ahány biten végzi a műveletet.

De, ha nagyon spórolni akarok, akkor akár össze is vonhatom a két műveletet egy áramkörbe, ugyanis az invertereket kiválthatom egy-egy XOR kapuval, ami annak függvényében invertálja A-t, ill. A XOR B-t, hogy a másik lába magas-e, lévén (0 ^ X) == X && (1 ^ X) == !X.

              *=======*
A ======+=====|       |           *=======*
        |     |   ^   |===+=======|       |
B =+====)=====|       |   |       |   ^   |=================== Y
   |    |     *=======*   |     *=|       |
   |    |                 |     | *=======*
F =)====)===+=========*   |     |
   |    |   |         |   |     |
C =)====)===)=========)===)=====+
   |    |   |         |   |     |
   |    |   |         |   |     |
   |  *=======*     *=======*   |
   |  |       |     |       |   |
   |  |   ^   |     |   ^   |   |
   |  |       |     |       |   | *=======* 
   |  *=======*     *=======*   *=|       |     *=======* 
   |      |             |         |   &   |=====|       |
   |      |             *=========|       |     |   |   |===== X
   |      |   *=======*           *=======*  *==|       |
   |      *===|       |                      |  *=======*
   |          |   &   |======================*
   *==========|       |
              *=======*

És így egy funkcióbittel (F) még azt is el tudom dönteni, hogy összeadni, vagy kivonni akarok és így gyakorlatilag két kapu áráért cserébe a kivonóáramkör összes többi része megspórolódott és cserébe még komplemenseket sem kell számolgatni (pre-invertálás, pre-inkrementálás).

Szóval tényleg nem értem, hogy miért lett volna ez bajosabb, pontosan ugyanazok az elemi kapuk szerepelnek benne, mint ami a 3-bites összeadóban van (AND, OR, XOR).

Leírtam, hogy "ha soros a kivonó az ALU-ban, akkor összesen ennyi, ha párhuzamos, akkor annyiszor kettő, ahány biten végzi a műveletet"; most olyan sok az a 16 plusz kapu mondjuk egy 8-bites CPU-ban?
De össze is lehet vonni a két funkciót és akkor nem kell invertálni, meg inkrementálni; az is kapukba kerül (meg időbe).

Ez nem válasz a kérdésre. Légy szíves avass be, hogy miért okozott elődeinknek akkora problémát az a CPU-nként 2 vagy 16 plusz kapu, ha közben megspórolhatták azokat a kapukat, amikkel az ALU-t a kivonás előtt invertálásra és inkrementálásra utasították. Nekem ez egy nagyságrenddel bonyolultabbnak tűnik, mint egy plusz bemenő jel és két plusz XOR kapu.

Szerintem pihizz inkább.

Tudod, létezik plusz nulla, minusz nulla, meg még egy rakat egyéb körülmény, amire figyelemmel voltak a korabeli mérnökök. Te viszont nem. Lehet, én sem.
Azt meg nem is tudom, hogy képzeled, hogy majd szögmérővel, méterrúddal, lézerkarddal méregetjük, hogy a "bajos" jelző kinél hány logikai kaput jelent? Netán deklaráljuk a kapuk számát mint mértékegységet, hogy konszenzusra jussunk, mennyi is egy "bajos"? 
 

Amúgy is, te magad írod, hogy bitenként két kapu pluszban.  A zongorát felvinni a tizenötödikre bajosabb, mint a tizennegyedikre.

 

Én azt kérdeztem, hogy nálad mit jelent, hogy bajosabban. Ha azt írod, hogy egyes ALU-kban így oldották meg, akkor szavam sincs, mert ez tény. Ha azt írod, hogy mert így spóroltak három tranzisztort az egész CPU-n, ami egymillió legyártott CPU-nál már hárommillió tranzisztor, akkor azt tudomásul veszem. De mit értesz azalatt, hogy "bajosabban"? Te mit értesz alatta? Eleddiglen még semmilyen értékelhető választ nem kaptam tőled erre az egyszerű kérdésre.
Ami pedig a mínusz nullát és plusz nullát illeti: a nullát, ha invertálod, majd inkrementálod, akkor ugyanúgy nullát kapsz, úgyhogy nem értem, hogy ez most hogy jött ide.

Sz*rk: Alámszerkesztettél. Már másodjára írom le, hogy párhuzamos logika esetén plusz két kapu bitenként, soros esetén összesen kettő. Azonfelül azt is leírtam már kétszer, hogy a komplementst is képezned kell, ami egy negálás (az is plusz egy kapu "bitenként"), meg egy inkrementálás (az egy egész léptetőregiszter N db flip-floppal), ráadásul úgy két lépés, ehhez képest így egy lépésből megoldottad az egészet.

Szerintem a kettes komplemens összeadás egyszerűbb. Ha teljes összeadót és kivonót csinálsz függetlenül, kell majd egy multiplexer, amely kiválasztja, melyikről jöjjön az eredmény. Kettes komplemens összeadásnál a kivonandó bitjeit egy-egy xor kapun küldöd át, a xor kapu másik bemenete jön az utasítás dekóderből aszerint, hogy add vagy sub az utasítás. Ha nem kell alulról carry-t fogadni, akkor ezt az utasítás dekóderből jövő bitet a bemenő carry-be is bekötheted, s máris megvan a kettes komplemens.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Ha teljes összeadót és kivonót csinálsz függetlenül, kell majd egy multiplexer, amely kiválasztja, melyikről jöjjön az eredmény.

Miért kell függetlenül? Össze lehet vonni a kettőt, ld. 3. ábra.

Kettes komplemens összeadásnál a kivonandó bitjeit egy-egy xor kapun küldöd át, a xor kapu másik bemenete jön az utasítás dekóderből aszerint, hogy add vagy sub az utasítás.

Azaz az összes bithez kell egy plusz XOR kapu.

Ha nem kell alulról carry-t fogadni, akkor ezt az utasítás dekóderből jövő bitet a bemenő carry-be is bekötheted, s máris megvan a kettes komplemens.

És akkor hogy működne pl. a 6502-es ADC utasítás, ha nem fogadsz alulról carry-t?

Lényegében beleintegráltad a multiplexert, hiszen az is egy kombinációs hálózat, függvényt egyszerűsíteni meg jó dolog. Különben a bitenkénti xor kaput is könnyen lehet, hogy a CPLD vagy FPGA fejlesztői környezete - Verilog, VHDL szavak jutnak eszembe - szintén egyszerűsíti majd összevonva az összeadás funkcióval.

Úgy fogalmaztam, hogy ha nem kell alulról carry-t fogadni, akkor teheted ezt meg.

Amúgy hihetetlen mértékben értelmetlen a vitánk. Van egy fekete doboz. Input két 8 bites operandus, a carry, valamint egy bit, ami eldönti, összeadás vagy kivonás lesz. Output egy 9 bites szám, vagy ha úgy tetszik, egy 8 bites és egy carry, de ez ugyanazt jelenti.

Ennek a doboznak egy pontosan felírható igazságtáblázata van. Függvényegyszerűsítés után egyetlen optimális megoldás lesz, de ezt a fejlesztői környezet megoldja. Legfeljebb azt tudom elképzelni, hogy azonos bonyolultságú megoldások jöhetnek ki, akkor érdekes lehet a propagation delay. Habár szerintem kétszintű hálózattal minden megoldható.

Szóval most arról beszélünk, hogy ki hogyan rajzolná ezt le a fejlesztői környezet számára, amelyből a fejlesztői környezet ugyanazokat a függvényeket írná fel, majd innentől nincs különbség az esetek között, egyszerűsít, és kiköpi a netlistát lényegében.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Lényegében beleintegráltad a multiplexert, hiszen az is egy kombinációs hálózat, függvényt egyszerűsíteni meg jó dolog.

Magyarul ott vagyok vele, mint nélküle, helyette komplemensképzéssel.

Különben a bitenkénti xor kaput is könnyen lehet, hogy a CPLD vagy FPGA fejlesztői környezete - Verilog, VHDL szavak jutnak eszembe - szintén egyszerűsíti majd összevonva az összeadás funkcióval.

Valahol csak képezni kell azt a komplemenst, ha az összeadót akarom kivonónak használni.

Úgy fogalmaztam, hogy ha nem kell alulról carry-t fogadni, akkor teheted ezt meg.

Értettem, csak szóltam, hogy kelleni fog.

Amúgy hihetetlen mértékben értelmetlen a vitánk. Van egy fekete doboz. Input két 8 bites operandus, a carry, valamint egy bit, ami eldönti, összeadás vagy kivonás lesz. Output egy 9 bites szám, vagy ha úgy tetszik, egy 8 bites és egy carry, de ez ugyanazt jelenti.

Ennek a doboznak egy pontosan felírható igazságtáblázata van. Függvényegyszerűsítés után egyetlen optimális megoldás lesz, de ezt a fejlesztői környezet megoldja. Legfeljebb azt tudom elképzelni, hogy azonos bonyolultságú megoldások jöhetnek ki, akkor érdekes lehet a propagation delay. Habár szerintem kétszintű hálózattal minden megoldható.

Szóval most arról beszélünk, hogy ki hogyan rajzolná ezt le a fejlesztői környezet számára, amelyből a fejlesztői környezet ugyanazokat a függvényeket írná fel, majd innentől nincs különbség az esetek között, egyszerűsít, és kiköpi a netlistát lényegében.

Asszem most két külön dologról beszélünk. Én arra kérdeztem rá, hogy Pixel5 mit értett azalatt, hogy a régi CPU-kban a kivonást bajosabban oldották volna meg, de ez a régi CPU-kra vonatkozott, te pedig - ha jól értem - modern fejlesztői környezetekről beszélsz.

Igen, de egyben elvekről is beszélek. Ha nem modern környezetben, hanem papír-ceruza módszerrel - Karnaugh tábla, Veitch tábla -, akkor is ugyanaz fog kijönni, csak sokkal tovább tart. :)

Azon persze lehet vitatkozni, hogy nem a legegyszerűbb megoldást választjuk, hanem modulárisan rajzolunk, nem egyszerűsítünk. Ebben az esetben a külön összeadó és kivonó különbözni fog a kettes komplemens képzéssel összeadóval történő kivonástól, de így több tranzisztort kell a chipre integrálni. Igaz, viszont sok egyforma, ismétlődő blokk lesz, bár ennek a chipen már aligha van előnye.

Viszont, ha egyszerűsítjük a függvént, ugyanannak kell kijönnie a végén, akárhogyan is futunk neki az elején, mégpedig azért, mert az egyes gerjesztővektorokra pontosan ugyanazon választ kell adnia a kombinációs hálózatunknak.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Hát ha a két áramkör különbözik, akkor igen, de láthattad, hogy az eredmény azonos módon jött ki, az átvitelhez meg kettő darab inverter kellett pluszba, ami egy-egy XOR kapuval feltételessé tehető. Most mondhatnánk, hogy ez így még mindig egy kapuval több per bit, mint az egyes komplemensképzés, de mi a helyzet az utána esedékes inkrementálással? Ahhoz a vezérléshez hány plusz kapu kell még, hogy az adatot összeadás előtt elküldje a léptetőregiszter felé, majd fogadja? (Egyáltalán hogyan illeszted bele az invertálás logikáját az összeadóba, ha azt az inkrementálás előtt kell megejteni? Akkor az még plusz egy lépés előtte, hogy invertáljuk a regisztert.) Lehet, hogy én hagyok ki valamit, de nekem olybá tűnik, hogy az a bonyolultabb megoldás, így meg egy lépésből van meg az egész, cserébe azért az egy db plusz XOR kapuért.