Régóta gondolkodtam egy saját számítógép megépítésén, és mostanra jutottam el odáig, hogy meg is tudjam valósítani. Az elkészítésben nagy segítséget nyújtott a SEM és a műhelyük, ahonnan alkatrészeket kaptam, és hibajavítás céljából egy nyákot is legyártottam.
A cikk egy saját tervezésű processzorról fog szólni, amit a Digit tárgyban már megismert regiszterek, számlálók és multiplexerek segítségével sikerült összeraknom. Igyekeztem a lehető legegyszerűbb (azaz legkevesebb hibalehetőséget tartalmazó) áramkört létrehozni.
A processzor két 10×10 cm-es nyákból áll, az egyiken a vezérlő egység, a másikon az aritmetikai egység kapott helyet. Maga a processzor 4-bites (azaz 0 és 15 közötti számokkal dolgozik). Az eredeti változatban sajnos elég sok hibát találtam, de az ide feltöltött tervrajzokban ezek már javítva vannak. Jelenleg a bemenetre 4 kapcsolót, a kimenetre pedig 4 LED-et raktam, de ez akár shift regiszterekkel is bővíthető.
Schematic: aritmetikai egység, vezérlő egység
Utasításkészlet:
Aritmetikai utasítások esetén az egyik operandus a 4-bites akkumulátor (A) regiszer vagy a 0. A másik operandus lehet egy szám, az INPUT külső bemenet, vagy egy adat a memóriából, illetve ezek kettes komplensei (azaz -1 -szeresei).
- add/sub: összeadás és kivonás (modulo 16), az átvitelbit minden műveletvégzés után alaphelyzetbe állítódik
- and/or/xor: bitműveletek
- mov: adatmozgatás, az A regiszer tartalma a megadott címre kerül az adatmemóriába, vagy az OUTPUT kimeneti regiszterbe
- jmp: ugrás, a programszámláló (PC) regiszter átállítása
- jz/jnz: feltételes ugrás, csak akkor ugrik, ha A zérus, illetve nem zérus
Az utasítások formátuma “utasítás érték”, pl. “add 5“, de az assembler bonyolultabb parancsokat is ismer, amiket visszaalakít a processzor számára érthetővé, pl. “inc @0” (ami C-ben ezt jelenti: memory[0]++ ) helyett “and 0, or @0, add 1, mov @0“.
Aritmetikai egység:
Itt található az adatmemória, amiben 16 darab 4-bites érték tárolható. A memória adatbuszán található még egy three-state buffer is, hogy írni és olvasni is lehessen belőle. Minden aritmetikai utasítás után számolás eredménye az A regiszterbe kerül. Ha “mov OUTPUT” utasítás érkezik, akkor az OUTPUT kimeneti regiszterbe másolódik az A regiszter értéke.
Maga a számítást végző rész egy mindössze 3 darab logikai kapus (azaz AND, OR, XOR) IC-ből álló kombinációs hálózat, ami az eredményt bitenként számítja ki, és beleshifteli az A regiszterbe. A bitek számítása között keletkező átvitelt és az eredmény zérusságát egy külön státuszregiszterben tárolom, ahonnan csak a zérus (Z) jelet vezettem ki. Emiatt egy műveletvégzés 4 órajelet igényel.
A maradék IC multiplexer, ami arra szolgál, hogy a operandust (szám, memória vagy INPUT) és a megfelelő művelet eredményét (add/sub, and, or vagy xor) kiválassza, illetve hogy a műveletek elején a carry és zero flageket alaphelyzetbe állítsa.
Vezérlő egység:
A feladata, hogy a programmemóriában az éppen a programszámláló (PC) által kijelölt címen található utasításnak megfelelően állítsa elő a vezérlőjeleket az aritmetikai egység számára. A PC regiszer két darab számlálóból áll, így összesen 256 utasítás megcímzésére alkalmas. A programmemória adat- és címvezetékei ki vannak vezetve, hogy pl. egy Arduino-val lehessen rá a programot feltölteni.
Egy aritmetikai számoláshoz 4 órajel szükséges, de még az aktuális utasítást több memóriacímről is ki kell olvasni (mivel minden utasítás 12 bites, de a memóriának csak 8 adatvezetéke van), ezért az egyszerűség kedvéért minden utasításnak 8 órajelnyi időt adtam, és egy számláló alsó 3 bite segítségével állítottam elő a vezérlőjeleket (pl. az adatmemória írása, az A regiszer léptetése, stb.).
Az itt található gombokkal lehet resetelni, és a végrehajtást szünetelni.
Példaprogramok (erős idegzetűeknek):
Az Arduinonak csak az a szerepe, hogy feltöltse a programokat, és még debuggerként is használható. Feltéve, hogy a program már beíródott a programmemóriába, csak egy külső órajel kell a működéshez, amit szintén az Arduino szolgáltat.
Egyszerű számláló, ami a bemenetről beolvasott számnak megfelelő ciklusnyit várakozik:
/* ezt valósítja meg a kód: counter = 0; while(true){ delay = INPUT; while(delay--) {} OUTPUT = counter++; } */ alias delay @0 //változók deklarálása alias counter @1 eq counter, 0 //értékadás loop: //ez egy címke eq delay, INPUT delayloop: jz break dec delay jmp delayloop break: eq OUTPUT, counter inc counter jmp loop
Megszámolja az 1-es biteket a bemeneten (popcount):
alias db @0 alias i @1 popcount: eq i, INPUT eq db, 0 loop: ld i jz exit and 0b1000 jz skip_inc inc db skip_inc: ld i add i mov i jmp loop exit: eq OUTPUT, db jmp popcount
Pszeudovéletlenszám-generátor:
/* lsfr -- linear-feedback shift register részletek: https://en.wikipedia.org/wiki/Linear-feedback_shift_register 15-ös periódusú 4-bites számsorozat előállítása while(true){ if (i & 1) { i = (i >> 1) ^ feed; } else { i = (i >> 1); } } a "feed" értéke csak 9 vagy 0xC lehet */ alias i @0 //így lehet változókat deklarálni alias j @1 //segédváltozó alias feed 9 //ez egy konstans nop init: eq i, INPUT //az INPUT nem lehet 0 lsfr: eq OUTPUT, i //először j = i >> 1 értéket kell kiszámítani //mivel nincs jobbra shiftelés, ezért bitvadászat következik //j = 0 eq j, 0 //j += i&0b1000 ? 0b0100 : 0 tst i, 0b1000 jz skip1 ld j add 0b0100 mov j skip1: //j += i&0b0100 ? 0b0010 : 0 tst i, 0b0100 jz skip2 ld j add 0b0010 mov j skip2: //j += i&0b0010 ? 0b0001 : 0 tst i, 0b0010 jz skip3 ld j add 0b0001 mov j skip3: //most már: j = i >> 1 tst i, 1 jnz if jz else //if(i&1){ if: //i = j^feed ld j xor feed mov i jmp endif //}else{ else: //i = j eq i, j //} endif: jmp lsfr