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