Alpha: První program

Nejjednodušší program bude obligátní „blikání LEDkou“. Po pravdě řečeno – zatím nemáme moc jiných způsobů, jak sepřesvědčit, že systém funguje, kromě zmíněného připojení rezistorů k datové sběrnici, simulace instrukce NOP a sledování změn na adresové sběrnici. Ale máme LED na výstupu SOD!

Zapojte tedy obě paměti k procesoru a zkusíme si naprogramovat první program. Bude to nějak takto:

A máme to, rachota skončila, můžeme pokračovat…

Vlastně ne, teď to teprve začne. Musíme tohle všechno naprogramovat v assembleru. Začneme inicializací.

Nejprve řekneme assembleru, že program bude začínat na adrese 0. Bývá dobrým zvykem si tento bod nějak pojmenovat – třeba návěštím RESET.

První instrukce je DI. Ta zakáže přerušení. Náš systém sice přerušení nepoužívá, ale patří k dobrému zvyku na začátku práce, než jsou nastavené všechny potřebné věci, zakázat přerušení. Kdyby nějaké přišlo dřív, než je nastavený například ukazatel zásobníku, systém by zhavaroval.

Další instrukce nastaví ukazatel zásobníku na konec paměti RAM. Ta u Alphy zabírá paměťový prostor 8000h – FFFFh. Takže nastavíme SP na hodnotu „o jedna vyšší než poslední volná adresa“ – tedy FFFFh+1 = 0000H. Instrukce LXI SP poslouží výborně.

Po této inicializaci máme procesor připravený na práci. Nemusíme inicializovat nic víc – ostatně v našem systému nic víc není. Můžeme tedy napsat tu smyčku:

Nejprve nastavujeme SOD na 1. K tomu slouží instrukce SIM. Připomeňme si: tato instrukce vezme hodnotu v registru A a podle ní nastaví SOD a masku přerušení (ta nás teď nezajímá). Pro nastavení musí být druhý nejvyšší bit (D6) roven 1. Pokud tomu tak je, procesor pošle hodnotu nejvyššího bitu (D7) na výstup SOD.

Následuje volání čekací smyčky. Tu zatím neřešíme, ta bude „někde, nějak, později“. Prostě jen skočíme na adresu,kde bude čekací rutina, a ta se pomocí instrukce RET zase vrátí zpátky.

Následuje analogicky nastavení SOD na hodnotu 0, opět volání čekací rutiny, a po ní skok zase na začátek celého procesu.

Tím je jádro programu hotové. Jak na tu čekací smyčku?

K čekání se mohou použít různé způsoby. Například instrukce NOP – zabere 4 takty, což u našeho systému představuje zhruba 2,17 mikrosekund. Pokud jich použijeme tisíc, bude to 2,17 milisekund, pokud sto tisíc, bude to 217 milisekund, tedy skoro čtvrt sekundy. To by šlo použít.

Teda, šlo, kdybychom mohli do paměti uložit sto tisíc instrukcí. Nemůžeme. 32768 je maximum. Co s tím?

Opět použijeme smyčku. Prázdnou, uvnitř se nebude dít nic. Jen budeme odečítat od zadané hodnoty k nule, a až dojdeme k té nule, smyčka skončí. Nějak takto:

Použijeme dvojici registrů D, E. Do ní si uložíme časovací konstantu, a pak budeme jen ve smyčce snižovat hodnotu DE o 1 a kontrolovat, jestli je výsledek 0. Pokud ano, tak skončíme, pokud ne, točíme se znovu.

Smyčka DLOOP začíná snížením hodnoty v dvojici registrů D a E – DCX D. Teď musíme zkontrolovat, jestli je výsledek 0. Bohužel (šestnáctibitová) instrukce DCX nenastaví příznak zero (to dělají jen instrukce osmibitové). Použije se opěttrik, totiž logický součet (OR) hodnot v registrech D a E. Výsledek bude 0, pokud D i E budou nulové. A protože nemáme takto obecnou instrukci, musíme si nejprve hodnotu z jednoho registru zkopírovat do akumulátoru a pakudělat OR s druhým registrem. Instrukce ORA podle výsledku nastaví příznak Z a my tedy můžeme podle toho skákat – instrukcí JNZ, což je vlastně JUMP if NOT ZERO.

A teď otázka za sto bodů: Jak dlouho ta smyčka bude probíhat? Jasně, záleží to na té konstantě a bude to přímo úměrné. Ale kolik to bude? Dalo by se to spočítat?

Inu, dalo. Vezměte si k ruce tabulku s instrukcemi a jejich trváním a napište si, kolik která instrukce zabere taktů:

Pokud bude konstanta = 1, proběhne tento cyklus přesně jednou. Zabere to tedy 10 + {6 + 4 + 4 + 7} + 10 = 41 T

Pokud bude konstanta = 2, proběhne tento cyklus takto:

10 + {6 + 4 + 4 + 10} + {6 + 4 + 4 + 7} + 10 = 65 T

Pokud bude konstanta = 3, poběží následujícím způsobem:

10 + {6 + 4 + 4 + 10} + {6 + 4 + 4 + 10} + {6 + 4 + 4 + 7} + 10 = 89 T

Je tedy vidět, že základní čekací doba je 41 T, a pokud zvýšíme konstantu o 1, zvýší to čekání o 24 T. Vzorec tedy je:

T = 41 + (N – 1) x 24

a z toho: N = (T – 41) / 24 + 1

Pokud bude konstanta rovna 100, bude čekání 41 + 99 x 24 = 2417 T.

Je to logické. První a poslední instrukce (LXI, RET) proběhnou vždy jen jednou (20T). Vnitřní smyčka jsou čtyři instrukce, a doba jejich provádění se liší podle toho, jestli už se dosáhlo nuly. Pokud ne, tak instrukce JNZ skáče a trvá 10T. Pokud ano, instrukce JNZ neskáče a trvá jen 7T. Průchod smyčkou tedy trvá 24T (6 + 4 + 4 + 10), pokud jde o poslední průchod, tak pouze 21T.

Vzorec můžeme samozřejmě zapsat jako T = 24 * N + 17

Co se stane, když bude konstanta rovna 0? No, hned první DCX D odečte jedničku od 0000h, operace přeteče a výsledek bude FFFFh. Smyčka se tedy vykoná nikoli nula krát, ale 65536x!

Kolik vlastně taktů potřebujeme takto „propálit“, aby bylo zpoždění třeba půl sekundy? Víme, že procesor pracuje s taktem 1,8432 MHz, to znamená, že za sekundu vykoná 1 843 200 taktů. Na půlsekundové zpoždění tedy potřebujeme polovinu, ergo 921600 taktů. Zanedbáme takty, které zabere volání podprogramu instrukcí CALL, časování není zde úplně kritické.

Magická konstanta N bude tedy:

N = (921600 – 41) / 24 + 1 = 38399 (zaokrouhleně)

A co kdybychom chtěli blikat ještě pomaleji, třeba se sekundovými čekacími smyčkami? No, je to prosté, použijeme dvojnásobnou konstan… aha! Dvojnásobná hodnota je 76798, a to se nám do registrů nevejde. Tak zavoláme půlsekundové čekání dvakrát!

Anebooo – co když do smyčky mezi DCX D a MOV naházíme několik NOPů? Třeba pět:

Pět NOPů, každý po 4T, to máme 20 T. O tuto hodnotu se zvýší čas nutný k průchodu smyčkou. Vzorec se tedy změní na:

T = 61 + (N – 1) x 44

Půlsekundové zpoždění s touto rutinou bude vyžadovat konstantu 20945, sekundové pak 41890. Platíme za to tím, že program zabere v paměti víc místa.

Všimli jste si drobné nevýhody celé rutiny? Pracuje s registry D, E a A. Co když ale v A jsou nějaké informace, které chceme zachovat?

Lze samozřejmě použít PUSH PSW a POP PSW, tedy uložit registr A a opět ho obnovit. A asi i rovnou uložit DE. Ale jsme v assembleru: neděláme nic, co není nutné!

Můžeme rozložit dekrement dvojice registrů D a E na dvě vnořené smyčky. V té vnitřní se bude dekrementovat hodnota v registru E, v té vnější hodnota v registru D. Díky tomu, že instrukce dekrementace osmibitového registru správně nastavuje příznaky, můžeme rovnou testovat, zda je výsledek operace 0:

Vzorec se trošku zesložití – zachována zůstane konstanta 20 T na první a poslední instrukci. Nejvnitřnější smyčka zabere 14 T / 11 T (pro E nulové): 14 x (E – 1) + 11

Vnější smyčka zabere (počet taktů vnitřní smyčky) + 14 T / 11 T pro D = 0. Nesmíme ale zapomenout na to, že hodnota E se uvažuje pouze při prvním průběhu, při dalších už je E = 256 (ve skutečnosti 0, ale ta se chová jako 256).Pokud je E=256, zabere vnitřní smyčka 3581 T.

Tedy:

(14 x (E – 1) + 11) + 11 (to je první průběh smyčkou E + hodnota pro poslední průběh smyčkou D) plus

(D – 1) x (14 + 3581) (průběh plnou smyčkou E + vlastní smyčka D)

T = 20 + 14 x (E – 1) + 11 + 11 + (D – 1) x (14 + 3581) = 42 + 14 x (E – 1) + 3595 x (D – 1)

Zásadní rozdíl proti předchozí verzi je, že registry D a E jsou dekrementovány nezávisle, takže pokud nastavíte hodnotu DE = 0001h, bude se to ve skutečnosti chovat, jako by v registru D byla hodnota 256. Nejnižší možná hodnota, při níž proběhne rutina lineárně bez jakýchkoli skoků je tedy 0101h. Pak zabere rovných 42 T. Pokud bude hodnota 0000h, bude to jako D=256 a E=256, tedy provedení bude trvat 920337 T. Což je o něco méně než půl sekundy…

Překlad a spuštění

Program máme napsaný, teď nastal čas ho přeložit. Spusťte si ASM80 (https://www.asm80.com) a kliknutím na New file založte nový soubor. Do něj napište výše uvedené příkazy. Uložte jej pod názvem test.a80 – přípona .a80 je důležitá, podle ní překladač pozná, že jde o program pro procesor 8080/8085). Klikněte na Compile.

V seznamu souborů vlevo se objeví dva nové soubory: test.a80.lst a test.a80.hex. První je výpis programu včetně operačních kódů a umístění v paměti, takzvaný listing:

Druhý (HEX) je kód, určený k naprogramování paměti. Stáhněte si jej (pravý klik na název, Download) a použijte k nahrání do paměti EEPROM. Konkrétní postup se liší podle zvoleného programátoru.

Paměť zapojte zpátky do systému a zapněte napájení. Pokud jste neudělali při zapojení nikde chybu, měla by dioda po chvilce blikat s frekvencí 1 Hz. Úvodní prodleva je dána velikostí RC článku na vstupu RESET.

Funguje? Gratuluji, právě jste si postavili vlastníma rukama osmibitový počítač a naprogramovali ho!

Námět na cvičení: Tato úloha by šla přepsat tak, aby nevyžadovala paměť RAM. Konstrukci tak můžete oživit jen s třemi IO: procesor, adresový latch a paměť ROM. Stačí místo volání podprogramu (CALL) zkopírovat čekací rutinu na dané místo. Dvakrát.

Příspěvek byl publikován v rubrice ASM80.com, Hardware, Software se štítky , , , . Můžete si uložit jeho odkaz mezi své oblíbené záložky.
  • gilhad

    Par poznamek
    – pokud uz ukaznene zaciname zakazanim preruseni, nebylo by tez ukaznene to preruseni po inicializaci zase povolit?
    – jeste k zakazani preruseni a nastaveni SP – nejak se domnivam, ze procesor je takovy ten typicky zlovolny urednik, co „jen plni instrukce“, takze by s neinicializovanym SP nezhavaroval, bezel by obmyslne dal, jen by pouzil jakoukoli hodnotu, co by v SP zrovna byla a tvaril se ze to je presne to, co jsme po nem chteli. (A dle zakona schvalnosti by tam pri prvnich pokusech po spusteni bylo 0000 a vse by fungovalo spravne a divit bychom se zacli az bychom na opomenuti inicializace zapomeli a spolehli se, ze to funguje – pak by se pri startu v SP urcite objevila ta nejmene vhodna hodnota)
    – ke cviceni – pokud uz jsme v hentom assembleru a nedelame co neni nutne, proc kopirovat podprogram dvakrat do pameti a nepouzit misto toho zatim nepouzity (dvoj)registr HL, kam bychom dali nasledujici adresu, do podprogramu skocili pomoci JMP a zpatky misto RET pomoci PCHL ? (pak bychom si mohli i odpustit inicializaci SP a predchozi DI a usetrit jeste dalsi byty, Navic mame jen ROM, takze SP stejne pouzit nejde …)