Minipočítač TWELVEKapitola 1 - zpátky do roku 1960"Na počátku bylo slovo, a to slovo mělo dvanáct bitů."On to vlastně nebyl úplný počátek, a navíc těch počátků bylo víc. Jeden z nich je instrukční sada Z80, kterou si díky mému lahosipu nedokážu zapamatovat celou, tak abych nemusel furt hledat v nějaké tabulce. Další začátek byl programování emulátoru a řešení přesnosti nebo věrnosti emulace. Například rozdílné doby zpracování instrukcí a vytváření odpovídajících zpoždění.
Takže jeden z cílů byla co nejmenší instrukční sada, obsahující jenom ty skutečně nejdůležitější instrukce, aby to aspoň něco mohlo dělat a žádnou jsem přitom nezapomněl.
Další cíl byla co nejpřesnější emulace, aby počítač fungoval úplně stejně jako skutečný, takže došlo vlastně na simulaci jednotlivých hradel, kde se ta podobnost realitě snad vytvoří tak nějak sama. Nebo aby to případně šlo postavit i ve skutečnosti, z TTL obvodů, mikromodulů, tranzistorů, relátek... Či FPGA.
A protože k něčemu takovému bych asi těžko sháněl dostatečnou dokumentaci odpovídající mým představám, musel jsem si to prostě vymyslet sám. Navíc se mi nechtělo něco napodobovat.
Existuje několik základních koncepcí počítačů, rozlišených právě jejich instrukční sadou.
Nejrozšířenější a nejpoužívanější už od dob sálových počítačů, protože čím víc proužků, tím víc adidas:
CISC - Complex Instruction Set ComputerMají spoustu luxusních instrukcí které toho umí hrozně moc, ale trvají proto spoustu taktů. Také komplikují práci kompilátorů. Jsou to třeba IBM System/360 a navazující, Z80, x86.
Jenže některým lidem se to nelíbilo a chtěli to udělat lépe a jednodušeji, za méně peněz víc muziky:
RISC - Reduced Instruction Set ComputerTady jsou vybrané nejpoužívanější a jednoduché instrukce, kterých je sice na stejný program potřeba víc, ale zase trvají mnohem méně taktů, takže ve výsledku pracuje počítač rychleji. Například ARM a 6502.
Nejjednodušší a nejlevnější počítače v historii ale uměly ještě méně, vystačily s minimem jako Diogenes:
MISC - Minimal Instruction Set ComputerTy mají jenom několik úplně nejzákladnějších instrukcí, často vykonávajících několik věcí najednou, takže se s nimi musí docela čarovat. Sem patří PDP-8 a LGP-30.
Volba je tedy jasná, bude to MISC. Existují sice i procesory s jednou instrukcí a dokonce i úplně bez instrukcí, ale do téhle černé magie se pouštět radši nebudu.
"Dvanáct bitů musí stačit každému."Prý řekli kdysi v Digital Equipment Company, a zdůvodnili to tím, že to stačí na počítání s přesností o řád vyšší než na logaritmickém pravítku.
Má to i další důvody, dvanáctibitové slovo se dá rozdělit na dvě šestibitové slabiky - byte, což je zase dost na minimální znakovou sadu.
Také se dá rozdělit na čtyřbitové nibbly a zapsat třemi šestnáctkovými číslicemi. Tak vznikl základní vzor kódování instrukcí, tři čtyřbitové skupiny:
CODE WORK DATAObsahuje kód instrukce, což předznamenává že jich může být šestnáct, potom číslo pracovního registru, a číslo pomocného, odkud se berou další data. Jsou to současně i názvy pomocných registrů ALU. Například sečtení dvou čísel:
ADD R0 R1Což znamená R0=R0+R1. Co se týká registrů, tak jsem si dovolil trochu luxusu, je jich celkem osm a všechny se dají použít na libovolnou činnost libovolným způsobem. Není tu žádný specializovaný akumulátor, takže odpadá otravné přesouvání sem a zase zpátky, protože je tu vlastně osm akumulátorů. Trochu opatrně se musí pracovat s
R7, který slouží současně jako
PROGRAM COUNTER, takže určité operace s ním nejsou úplně bez vedlejších efektů. Někdy však žádoucích.
Osm registrů se dá adresovat třemi bity, a co s tím čtvrtým? Ten určuje jestli se obsah registru použije přímo (0), nebo jako ukazatel do paměti (1). Dvanáctibitové adresy stačí na 4096 míst na magnetickém bubnu, kdysi velmi oblíbené součásti levných počítačů. Kdo četl
"Story of Mel", ví.
Takže k číslu na adrese v registru R0 přičteme číslo na adrese v registru R1:
ADD M0 M1To je jediný způsob adresování paměti, nic víc není potřeba.
Dá se to samozřejmě kombinovat, a bude se to moc hezky ručně nacvakávat přepínači na čelním panelu:
1111 1000 1001Kromě registrů jsou potřeba i nějaké příznaky, pro začátek aspoň CARRY pro počítání s vyšší přesností. Dvanáctibitové slovo má bity 0 až B, takže CARRY se může umístit do třináctého bitu C. Aby se to nepletlo. A uložit ho bokem, pro všechny registry společně.
"Méně někdy znamená více."To méně je přesně šestnáct instrukcí, ale které by to měly být? Počítač by měl především umět počítat, takže nějaké ty aritmetické - sčítání, odečítání, zvýšení a snížení o jedničku.
Aby se dalo i násobit a dělit, hodí se posun vlevo a vpravo.
Také je potřeba čísla přehazovat z místa na místo, nebo přímo programem zadat nějakou hodnotu.
Hodilo by se otestovat nebo změnit hodnotu nějakého bitu.
Není na škodu porovnání dvou čísel a podle toho provést podmíněný skok.
A nakonec program musí komunikovat s nějakými vstupně/výstupními kanály, za kterými se schovávají periférie.
Po nějakých těch změnách a slepých uličkách mi z toho nakonec vyšel tenhle slovníček, v abecedním pořadí:
================================================================================
ADD WORK DATAK registru WORK se přičte registr DATA a příznak CARRY. Výsledek se uloží zpět do WORK, případné přetečení se zase uloží do CARRY pro přenos do vyššího řádu. Sčítat bez CARRY nejde, takže před začátkem počítání je potřeba příznak vynulovat.
Kód:
Příklad:
ZERO CARRY
ADD R0 R4
ADD R1 R5
Počítání s dvacetičtyřbitovými čísly.
================================================================================
COPY WORK DATADo registru WORK se zkopíruje obsah registru DATA. Mě se nikdy nelíbilo říkat tomu přesun, když se vlastně vytvoří kopie a původní obsah stále zůstává tam kde je. Takže žádný MOV, ale COPY.
Kód:
Příklad:
COPY R0 M5
COPY R7 M0
Z adresy v registru R5 se načte hodnota do R0, ta se také použije jako adresa, a od ní se bude pokračovat v provádění programu. Pomocí instrukce COPY se tedy dá provést i skok, například jako nekonečný cyklus v programu.
Instrukce COPY R0 R0 nahrazuje neexistující instrukci NOP, která také ve skutečnosti nic neudělá, jenom spotřebuje trochu času. A operační kód je symbolicky
0000 0000 0000.
================================================================================
DEC WORKDekrementace registru WORK. Jak praví stará pravda:
Inkrement je přičtení jedničky.
Dekrement je odečtení jedničky.
Exkrement je výsledek chybného přičítání a odečítání jedniček.Tady jsou využité jenom první dva nibbly operačního kódu, poslední může obsahovat cokoliv, obsah se ignoruje.
================================================================================
EQUAL WORK DATAPorovná dvě čísla, pokud jsou stejná, provede se následující instrukce. V opačném případě se vynechá. Stejně jako
IF WORK=DATA THEN PŘÍKAZ.
Registr R7 má na rozdíl od ostatních jednu speciální vlastnost, funguje také jako čítač, takže je možné ho jednoduše inkrementovat. Porovnání proběhne tak že se obě čísla odečtou, a pokud je výsledek nenulový, přičte se jednička k R7, který v tom okamžiku ukazoval na příští instrukci programu. Tím se přeskočí a pokračuje se až tou následující.
Kód:
Příklad:
EQUAL R2 R3
COPY R7 R0
COPY R7 R1
Což znamená
IF R2=R3 THEN GOTO R0 ELSE GOTO R1. Tedy úplné větvení programu. To jsou ty žádoucí vedlejší efekty. Pomocí nich se dá testovat i nerovnost.
Kód:
Příklad:
EQUAL R2 R3
INC R7
COPY R7 R0
Takže vznikne
IF R2<>R3 THEN GOTO R0.
================================================================================
GREAT WORK DATAPro něj platí totéž co pro EQUAL, jen s rozdílem že následující instrukce se provede když je WORK větší než DATA, tedy
IF WORK>DATA THEN PŘÍKAZ.
Kód:
Příklad:
GREAT R2 R3
COPY R7 R0
To provede
IF R2>R3 THEN GOTO R0. Ovšem podmínka se dá i otočit.
Kód:
Příklad:
GREAT R3 R2
COPY R7 R0
Teď vzniklo
IF R2<=R3 THEN GOTO R0.
================================================================================
HALF WORKPosun registru WORK vpravo - dělení dvěma. On to vlastně není posun ale rotace přes CARRY, opět kvůli počítání s vyšší přesností než jen dvanáct bitů. A stejně jako u ADD platí že CARRY je potřeba předem vynulovat.
Kód:
Příklad:
ZERO CARRY
HALF R1
HALF R0
Tady se musí posunovat nejdřív vyšší řády, a až po nich nižší, naopak než u sčítání.
Opačná operace, tedy posun vlevo - násobení dvěma, nemá vlastní instrukci, ale dá se provést jinak, pomocí přičtení sebe
Kód:
sama.
Příklad:
ZERO CARRY
ADD R0 R0
ADD R1 R1
Ona tam tedy původně byla, ale když se ukázalo že se dá snadno nahradit, udělala místo další. Takže ve skutečnosti je tu instrukcí víc než těch šestnáct, jenom jsou některé dobře maskované.
================================================================================
INC WORKInkrement, opak dekrementu.
================================================================================
JUMP +-OFFSETRelativní skok. Tahle instrukce porušuje základní formát, protože má tvar
1000 Z OOOOOOO. Znaménko Z určuje kterým směrem se skočí, vpřed či vzad, za ním následuje sedmibitové číslo udávající vzdálenost - počet přeskočených slov od následující instrukce.
Kód:
Příklad:
ADD R0 R1
DEC R5
GREAT R5 R4
JUMP -4
Tenhle cyklus přičte R1 k R0 tolikrát, o kolik je R5 větší než R4.
Relativní skok dosáhne pouze 127 slov daleko, pro větší vzdálenosti je nutný absolutní skok pomocí zásahu do R7.
================================================================================
LESS WORK DATAOpak GREAT, následující instrukce se provede když WORK je menší než DATA, takže
IF WORK<DATA THEN PŘÍKAZ.
Kód:
Příklad:
LESS R2 R3
COPY R7 R0
To provede
IF R2<R3 THEN GOTO R0. Ovšem podmínka se dá i tady otočit.
Kód:
Příklad:
LESS R3 R2
COPY R7 R0
Teď vzniklo
IF R2>=R3 THEN GOTO R0.
================================================================================
NUMBER WORK čísloJediná instrukce s délkou dvou slov, všechny ostatní se vejdou do jednoho. Takže se jako jediná nedá vykonat podmíněně, aspoň ne přímo. Vnitřně se provede jako
COPY WORK M7, současně se inkrementuje R7 na instrukci následující po čísle.
Kód:
Příklad:
NUMBER R0 0
NUMBER R7 100
Do registru R0 se vloží číslo 0, a potom se spáchá nepodmíněný skok na adresu 100.
================================================================================
ONE WORK BITNastaví určený bit pracovního registru nebo příznak na jedničku. Pomocí jednoho nibblu se dá adresovat až šestnáct bitů, což obnáší dvanáct datových bitů 0 až B, systémový příznak CARRY, a volně použitelné příznaky D, E a F. Podle potřeby může E znamenat například ERROR, kdy jedna část programu signalizuje jiné že došlo k nějaké chybě při výpočtu, F je prostě FLAG pro všeobecné použití, když je potřeba nějaký bit někam na chvilku odložit.
Kód:
Příklad:
ZERO FLAG
EQUAL R0 R1
ONE FLAG
Prostě
IF R0=R1 THEN FLAG=1 ELSE FLAG=0.
Při nastavování příznaků není potřeba zadávat pracovní registr, respektive se dá použít kterýkoliv, jeho datová část se opět uloží nezměněná. Jen není vhodné v takovém případě použít adresu na bubnu, to by mohlo trvat dost dlouhou dobu.
Samozřejmě je možné nastavit jakýkoliv bit na libovolném místě bubnu, stejně jako ve kterémkoliv registru.
Kód:
Příklad:
NUMBER R0 555
ONE M0 0
Nastaví na jedničku nejnižší bit slova na adrese 555.
================================================================================
READ WORK CHANNELPřečte do pracovního registru hodnotu z určeného vstupního kanálu. Těch může být také šestnáct, stejně jako bitů, a mohou být současně vstupní i výstupní. Na kanálu 0 je čelní panel počítače, tedy stav přepínačů na něm. I těch je celkem šestnáct, protože se kromě dvanácti datových bitů čtou i příznaky, pomocí kterých může periférie informovat program o svém stavu.
Kód:
Příklad:
READ R7 0
Přečte stav přepínačů na čelním panelu, a skočí na zadanou adresu.
================================================================================
SUB WORK DATAOdečítání, pracuje přesně opačně než ADD. CARRY tady funguje jako BORROW, a také je ho potřeba před odečítáním vynulovat. SUB dovede také jiné věci než je počítání.
Kód:
Příklad:
ZERO CARRY
SUB R0 R0
SUB R7 R7
Nejdřív se vymaže obsah registru R0. Potom se stejným způsobem vynuluje R7, což znamená skok na adresu 0, tedy softwarový RESET.
================================================================================
TEST WORK BITPokud je zadaný bit 1, provede se následující instrukce, stejně jako u EQUAL/GREAT/LESS.
Kód:
Příklad:
READ R0 0
TEST FLAG
JUMP -3
Dokud je zapnutý přepínač bitu F na čelním panelu, program se zacyklí a čeká.
================================================================================
WRITE WORK CHANNELOpak READ, zapíše registr WORK a příznaky do zvoleného kanálu.
Kód:
Příklad:
NUMBER R0 555
WRITE M0 0
Zobrazí na kontrolkách čelního panelu obsah adresy 555 na bubnu a příznaky.
================================================================================
ZERO WORKOpak ONE, vynuluje vybraný bit pracovního registru nebo příznak. Což už jste asi všichni pochopili z předchozích příkladů.
================================================================================
Některé názvy instrukcí jsou trochu neobvyklé, ale je to proto, aby v zápisu programu stačilo použít jen počáteční písmeno, zbytek slova bude assembler ignorovat. Přece jen místa na bubnu není nazbyt, a i při pakování dvou znaků do jednoho slova by se na něj moc dlouhý zdroják nevešel. Zvlášť pokud půlku paměti zabere samotný editor textu.
Pro usnadnění práce je možné v assembleru použít i symbolické adresy a pár dalších direktiv.
Kód:
úplný zápis zkrácený význam
BEGIN 100 B 100 program bude umístěný od adresy 100
NUMBER R0 POINT 1 N R0 P1 do R0 se vloží adresa bodu POINT 1
COPY R1 M0 C R1 M0 do R1 se načte první číslo
INC R0 I R0 posun na následující adresu
ADD R1 M0 A R1 M0 k R1 se přičte druhé číslo
WRITE R1 0 W R1 0 výsledek se zobrazí na čelní panelu
DEC R7 D R7 program se navždy zastaví
POINT 1 P1 bod kde začínají data
VALUE 28 V 28 první číslo
VALUE -7 V -7 druhé číslo
QUIT 100 Q 100 konec překladu a spuštění programu od adresy 100
Assembler sám vypočítá skutečné adresy jednotlivých bodů a dosadí je do strojového kódu.
TWELVE nemá žádný hardwarový zásobník, a také nemá žádné instrukce pro volání podprogramu nebo návrat z něj. Ale dá se to nahradit softwarově, nejjednodušší způsob je připravení návratové adresy do volného registru (když jich je dost).
Kód:
Příklad:
COPY R6 R7
NUMBER R7 P99
...
...
POINT 99
...
INC R6
INC R6
COPY R7 R6
Na zavolání podprogramu jsou potřeba tři slova, na návrat také.
A protože každý registr je možné použít jako ukazatel do paměti, tak TWELVE dokáže pracovat až se sedmi softwarově ovládanými zásobníky současně! To umožňuje i vícenásobné vnoření, jen je místo R6 potřeba použít M6 a kolem toho inkrementovat/dekrementovat ukazatel na vrchol zásobníku.
Tak, to by aspoň na kalkulačku mohlo stačit, ne?