Showing posts with label SmallC. Show all posts
Showing posts with label SmallC. Show all posts

Thursday, May 23, 2013

SCC11. SmallC - PMI80

Neuveriteľné (pre mňa) sa stalo skutočnosťou. Na jednodoskáči s klávesnicou a sedemsegmentovým displayom beží program napísaný v C. Trocha to rozpíšem. Jedná sa o PMI80. Ten program v C sa samozrejme nezadáva cez klávesnicu a ani sa nekompiluje na PMI80. Program sa kompiluje pomocou SmallC na PC a napáli sa do EPROM. Je to o dosť zdĺhavejšie ako mať k dispozícii veľkú RAM a sériový bootloader. Práve preto som kedysi postavil NCB85.

Tu beží program v C
Istý Jakub si na aukro.cz kúpil toto PMI80 z obrázku. A nedalo mu to, skúsil na ňom C spôsobom ako som opísal. A nutno povedať, že s úspechom.


Data a Code segmenty
Pri preklade do RAM je vcelku jedno, kde sa nachádza program a kde premenné. Iné je to ale pri použití ROM. Program je v ROM, pretože jeho obsah je pevne daný a nemení sa. Ak teda zrovna nepoužívame samomodifikujúci sa kód. Ale premenné musia byť v RAM. Na toto majú assemblery možnosť definíce dátového a kódového segmentu alebo aj ďalších segmentov. Linker potom priradí každému segmentu adresu a vygeneruje správne skoky a návestia. Ja som nazval v SmallC dátový a programový segment takto:
Programový kód je umístěn do oblasti uvozené:
.area SMALLC_GENERATED (REL,CON,CSEG)
globální proměnné pak do:
.area SMALLC_GENERATED_DATA (REL,CON,DSEG)
Jakub poslal aj popis ako sú uložené dáta a program.
Lokální proměnné jsou jako obvykle umísťovány na stacku. Pro použití s PMI-80 je potřeba oblast SMALLC_GENERATED_DATA přesměrovat někam mezi adresy 0x1c00 a 0x1fff, kde je jediný 1KB RAM (přičemž je třeba mít na zřeteli, že od konce RAM směrem dolů roste stack). Já programová data umísťuji od adresy 0x0800 kde na mém upraveném PMI začíná třetí KB ROM a je to začátek druhého EPROM čipu. Programy do PMI dostávám pomocí mazačky a programátoru EPROM. Majitel neupraveného PMI bude muset linkovat na adresu 0x0400, nebo také do RAM (ale opravdu jí není mnoho). Já zatím do RAM program nelinkoval, protože stejně zatím nemám žádný loader, kterým bych ho bez použití naprogramované EPROM dostal na místo.
takto vyzerá výpis na display
update_disp(mpx,seg)
   unsigned char mpx;
   unsigned char seg;
   {
     mpx;
#asm
     mov b,l
#endasm
     seg;
#asm
     mov a,l
     out 0xF8
     mov a,b
     out 0xFA
#endasm
   }
a takto krátený hlavný program
for (a = 0; a < 37; a++) { //pres celou tabulku znaku v kodu monitoru
   disp_buff[0] = (a >> 4) & 0x0f;
   disp_buff[1] = (a ) & 0x0f;
   disp_buff[2] = PMI_CH_equal;
   bin2bcd(a, &disp_buff[3]);
   disp_buff[6] = PMI_CH_equal;
   disp_buff[7] = PMI_CH_equal;
   disp_buff[8] = a;

   for (c = 0; c < 100; c++) {
     for (b = 0; b < 9; b++) {
       update_disp(~b, seg_data[disp_buff[b]]);
       delay(10);
     }
   }
}
C na PMI80. Kto by si to bol kedy pomyslel ? Kto by mal záujem o podobné hrátky bude sa musieť spojiť s Jakubom. Ja PMI80 nevlastním ;-)

Saturday, May 11, 2013

SCC.10 printf

printf je užitočná a pomerne silná metóda, ktorá je prítomná v prekvapujúco veľa jazykoch. Okrem iných aj Fortran, Cobol, Java, PHP, Perl .. Mňa na nej vždy fascinovalo to, že má premenlivý počet parametrov. Napríklad:

printf("V zivote je %d dobrých vecí, hlavne %s stojí za to", my_number, her_name);

V tomto prípade má 3 parametre. Prvý je string obsahujúci formattery a nasledujú hodnoty pre formattery. Prvý je číslo ktorá sa zobrazí v decimálnom tvare a druhý parameter je string ktorý sa len prekopíruje na zodpovedajúcu pozíciu. Dlho som si lámal hlavu nad tým, ako len môže byť printf napísaný ... Veď čo ak by tie parametre boli štyri alebo povedzme sedem prípadne iba jeden ? Dosť záhadné :-)

Tajomstvo printf je jednoduché. C robí PUSH parametrov metódy na zásobník v opačnom poradí. Najskôr sa PUSHne posledný param a potom druhý od konca a tak ďalej. Až na koniec sa PUSHne prvý parameter. A to znamená, že sa nachádza hneď nad návratovou adresou, ktorú PUSHlo volanie našej metódy CALL printf

Preto stačí v implementácii printf získať obsah dvoch byte nad aktuálnym SP. To bude pointer na string obsahujúci formattery. A sme doma :-) Pretože z toho stringu už vieme zistiť koľko parametrov má naše volanie printf. A každý z nich sa nachádza zase o dva bytes vyššie.

Metóda printf nie je súčasťou SmallC, ale nie je problém ju napísať. Ja sa na to chystám už mesiac ;-) Sú tu ale dve veci. Dobrá a zlá. Zlá správa je, že v SmallC sa parametre volania ukladajú na stack v opačnom poradí ako je špecifikácia C. Čiže pascalovským spôsobom a tým pádom bude formátovací string najďalej od SP. Takže sa to nedá ? Ale dá, je tu aj dobrá správa. SmallC vie posielať do volanej metódy okrem parametrov (ktoré su PUSHnuté na stacku) aj počet parametrov. Posiela sa v ACC. Takže SmallC implementácia bude neštandardná. Počet parametrov si nezistí sama, ale bude sa spoliehať na číslo v ACC.

Aby to bolo úplne cool, bolo by fajn napísať to celé v ASM. Ak by to niekto chcel urobiť, môžem mu poslať nejaké podklady.

Monday, March 25, 2013

NCB85 5. File I/O

Prvý program Hello World overil funkčnosť konzolového I/O a cross compilácie. Druhý program otestoval spracovanie vstupných parametrov z CP/M príkazového riadku. Ďalším krokom je test file systému. Pripravil som krátky program, ktorý vypíše na konzolu obsah súboru. Názov súboru sa zadáva ako parameter. Ak sa nezadá žiadny parameter, alebo viac ako jeden parameter, program vypíše usage. Ak sa nepodarí otvoriť súbor, tak vypíše chybové hlásenie.
/*
* File: main.c, march 2013
* file test on CP/M
*/
#include <stdio.h>

FILE *input;

/**
* main routine
* @return
*/
main(int argc, int argv[]) {
    char k;
    print("File test\r\n");

    if (argc == 2) {
       print("file:");
       print(argv[1]);
       print("\r\n");

       if ((input = fopen(argv[1], "r")) == NULL) {
          print("Open failure\n");
          exit(1);
       }

       while ((k = fgetc (input)) != EOF) {
          putchar(k);
       }

       fclose(input);
    } else {
       print("usage: fltst file.ext");
    }
}

print(char *str) {
    while (*str) {
       putchar(*str++);
    }
}

CRTSO sa zase trošku zmenilo. Pribudla inicializácia STDIO systému. Toto je asi aj finálna verzia. Už tam je všetko, čo je potrebné na prácu v CP/M.
    lxi h,0
    dad sp
    shld stksav ; save the stack pointer
    lhld 6 ; pick up core top
    lxi d,-10 ; decrease by 10 for safety
    dad d
    sphl ; set stack pointer
    call stdioinit ; initialize stdio
    lxi h,0x0080
    push h
    call Xarglist
    lhld Xargc
    push h
    lxi h,Xargv
    push h
    call main ; call main program
    jmp 0
stksav: .ds 2
crtsoend:
    .end

Do projektu pribudli ďalšie podporné súbory: io8080.c, bdos1.c a exit.c

Building project
scc8080 -t -a main.c
as8085 -l -o -s -p -g main.s
scc8080 -t -a bdos.c
as8085 -l -o -s -p -g bdos.s
scc8080 -t -a chio8080.c
as8085 -l -o -s -p -g chio8080.s
scc8080 -t -a arglist.c
as8085 -l -o -s -p -g arglist.s
scc8080 -t -a charclass.c
as8085 -l -o -s -p -g charclass.s
scc8080 -t -a io8080.c
as8085 -l -o -s -p -g io8080.s
scc8080 -t -a bdos1.c
as8085 -l -o -s -p -g bdos1.s
scc8080 -t -a exit.c
as8085 -l -o -s -p -g exit.s
as8085 -l -o -s -p -g cret.asm
as8085 -l -o -s -p -g crun8080lib.asm
aslink -n -i -u -o -m -b CRET=0x0100 -b LIB8080=crtsoend+0x0100 -b SMALLC_GENERATED=crun8080_end+crtsoend+0x0100 cmd cret.rel crun8080lib.rel main.rel bdos.rel chio8080.rel arglist.rel charclass.rel io8080.rel bdos1.rel exit.rel
sort -k1.8,1.9 -k1.4,1.7 cmd.ihx > cmd.hex
hex2bin -e com -c cmd.hex
hex2bin v1.0.1, Copyright (C) 1999 Jacques Pelletier
Lowest address = 00000100
Highest address = 00001DD3

Build finished


Výsledný COM sa nafúkol na cca 7.5kB. Z toho sú ale 2kB buffer pre čítanie sektorov z filesystému. To preto, lebo je dovolené mať otvorené naraz až 4 súbory. Odhadom ďalšie 0.5kB sú pomocné štruktúry ako 4x FCB atď.

NCB85 4. Command line params

Hello World bol prvý krok. Ďalší krok je otestovanie parametrov zadávaných v príkazovom riadku CP/M. CRTSO bude trošku zložitejšie.

; Run time start off for Small C.
.module CRET
.area CRET (REL,CON) ;program area CRTSO is RELOCATABLE
.list (err, loc, bin, eqt, cyc, lin, src, lst, md)
.nlist (pag)
.globl crtsoend

lxi h,0
dad sp
shld stksav ; save the stack pointer
lhld 6 ; pick up core top
lxi d,-10 ; decrease by 10 for safety
dad d
sphl ; set stack pointer
lxi h,0x0080
push h ; line buffer is at 0080H
call Xarglist
lhld Xargc
push h
lxi h,Xargv
push h
call main ; call main program
jmp 0 ; warm boot, reload CCP
stksav: .ds 2
crtsoend:
.end

Musel som upraviť dodaný CRTSO k SmallC pretože nefungoval. Pridal som riadok s LXI H,0080 a PUSH H. Následne volaná C rutina Xarglist totižto očakáva vstupný parameter char pointer na buffer s obsahom príkazového riadku. Rozparsuje buffer podľa medzier a naplní Xargc a Xargv globálne premenné. Pôvodný stack ukazoval na priestor v CCP, kde bolo miesto na 8 úrovní. Túto hodnotu síce ukladáme, ale po ukončení sa aj tak nepoužije. Jednoduchšie je zavolať warm boot, ktorý reloadne CCP.

C program

/*
* File: main.c, march 2013
* command line arguments testing on CP/M
*/

/**
* main routine
* @return
*/
main(int argc, int argv[]) {
    int i;
    print("Argument test\r\n");

    for (i=1; i < argc; i++) {
       print("arg:");
       putchar('0' + i);
       print(", value:");
       print(argv[i]);
       print("\r\n");
    }
}

print(char *str) {
    while (*str) {
       putchar(*str++);
    }
}

V main metóde sa zmenila hlavička. Už máme dva parametre. Prvý je počet parametrov argc. Ale čo ten druhý ? Malo by tam byť napr. char *argv[]. Čiže pole pointerov na char. Žial SmallC umožňuje iba jednu úroveň indirekcie. Využíva sa tu trik, že pointer je rovnako veľký ako int. Takže to pole integerov je vlastne to isté ako pole pointerov. Toto je ďalšia oblasť, kde by sa mohlo SmallC vylepšiť.

Spustenie programu na CP/M

A0>cmd a b -t
Argument test
arg:1, value:A
arg:2, value:B
arg:3, value:-T


To vyzerá dobre :-)

Build

K main.c, bdos.c a chio8080.c pribudli ďalšie dva súbory: arglist.c a charclass.c.
Building project
scc8080 -t -a main.c
as8085 -l -o -s -p -g main.s
scc8080 -t -a bdos.c
as8085 -l -o -s -p -g bdos.s
scc8080 -t -a chio8080.c
as8085 -l -o -s -p -g chio8080.s
scc8080 -t -a arglist.c
as8085 -l -o -s -p -g arglist.s
scc8080 -t -a charclass.c
as8085 -l -o -s -p -g charclass.s
as8085 -l -o -s -p -g cret.asm
as8085 -l -o -s -p -g crun8080lib.asm
aslink -n -i -u -o -m -b CRET=0x0100 -b LIB8080=crtsoend+0x0100 -b SMALLC_GENERATED=crun8080_end+crtsoend+0x0100 cmd cret.rel crun8080lib.rel main.rel bdos.rel chio8080.rel arglist.rel charclass.rel
sort -k1.8,1.9 -k1.4,1.7 cmd.ihx > cmd.hex
hex2bin -e com -c cmd.hex
hex2bin v1.0.1, Copyright (C) 1999 Jacques Pelletier
Lowest address = 00000100
Highest address = 00000827
Build finished
Výsledný COM už narástol na 727 bytov hexadecimálne.

Sunday, March 24, 2013

NCB85 3. Cross compiled Hello World

Používať moderné IDE na vývoj pre CP/M nie je žiadna prevratná myšlienka. Fakt ale je, že to skoro nikto nerobí. Snáď preto, že starí CP/M harcovníci majú odpor k Windows, alebo možno preto, že iba nepoznajú silu a výhody moderných IDE. Preto som sa do toho vrhol ja.

Každý C program začína metódou "main()". Lenže v CP/M sa programy spúšťajú od adresy 0x0100. Ako na to ? Písať k main niečo ako ORG 0x0100 by nebolo to pravé orechové. Robí sa to inač. Každý program v C má zavádzač. Nazýva sa C Run Time Start Off, skrátene CRTSO. Pre CP/M by mohol vyzerať takto nejako:
;   Run time start off for Small C.
;   cseg
    .module CRET
    .area CRET (REL,CON) ;program area CRTSO is RELOCATABLE
    .list (err, loc, bin, eqt, cyc, lin, src, lst, md)
    .nlist (pag)
    .globl crtsoend
    lxi h,0
    dad sp
    shld stksav
    lhld 6 ; pick up core top
    lxi d,-10 ; decrease by 10 for safety
    dad d
    sphl ; set stack pointer
    call main ; call main program
    lhld stksav ; restore stack pointer
    sphl
    ret ; go back to CCP
stksav: .ds 2
crtsoend:
    .end
Všimnime si volanie "call main". Áno to je ono. Tu nám zavádzač spúšťa našu main metódu. Tá bižutéria okolo, je už len presunutie zásobníka. Vlastnej tvorbe a fantázii sa medze nekladú. Musel som upraviť SmallC verziu pretože, neviem prečo ale bola zlá. Hneď na začiatku bolo SPHL. Nastavenie SP na hodnotu v HL. Ale to je asi blbosť. Veď CP/M nedáva v HL žiadnu hodnotu hovoriacu o zásobníku. Zase keby som nechal SP bez zmeny, tak by ukazoval niekam do CCP kde je priestor iba pre 8 úrovní. Preto som zvolil inú taktiku. Radšej si ho alokujem tam kde bolo CCP a miesta mám habadej.

C zdrojáky

SmallC nám všetky C zdrojáky preloží do ASM. Ďalší krok je spustiť assembler ktorý spracuje tieto vzniknuté ASM. Posledný krok je link. Linker nám musí zostaviť výsledný program, pozostávajúci z viacerých REL súborov. REL sú relokovateľné fragmenty. Je to výsledok volania assemblera. Linker ich zostaví do jedného celku, pričom na adrese 0x0100 bude ako prvý CRTSO. Ostatné fragmenty už môžu ísť v ľubovoľnom poradí za ním. Pri Hello World si vystačíme s tromi C súbormi.

/*
* File: main.c, march 2013
* hello world on CP/M
*/

/**
* main routine
* @return
*/
main() {
    print("Hello World!!");
}

/**
* prints zero terminated string
* @param str
* @return
*/
print(char *str) {
    while (*str) {
       putchar(*str++);
    }
}


Druhý súbor je z knižice SmallC. Je to chio8080.c
#define EOL 10
getchar() {
    return (bdos(1,1));
}

putchar (c) char c; {
    if (c == EOL) bdos(2,13);
    bdos(2,c);
    return c;
}


BDOS služba 1 je vstup jedného znaku z konzoly. BDOS služba 2 je výstup jedného znaku na konzolu. Posledný súbor je bdos.c ktorý už len zavolá CALL 5 volanie CP/M BDOS. Listing neuvádzam. Je to len CALL 5 a nejaké POP a PUSH.

COM file

Výstupom linkera je Intel HEX súbor. CP/M potrebuje spustiteľný COM file. Je možné nahrať na CP/M stroj HEX súbor a pomocou príkazu LOAD ho prekonvertovať na COM. Dá sa to ale aj priamo na Windows. Použijeme utilitu hex2bin, ktorá robí presne to isté. Naviac nám do build okna vypíše aj veľkosť výsledného COM.

Building project
scc8080 -t -a main.c
as8085 -l -o -s -p -g main.s
scc8080 -t -a bdos.c
as8085 -l -o -s -p -g bdos.s
scc8080 -t -a chio8080.c
as8085 -l -o -s -p -g chio8080.s
as8085 -l -o -s -p -g cret.asm
as8085 -l -o -s -p -g crun8080lib.asm
aslink -n -i -u -o -m -b CRET=0x0100 -b LIB8080=crtsoend+0x0100 -b SMALLC_GENERATED=crun8080_end+crtsoend+0x0100 hello cret.rel crun8080lib.rel main.rel bdos.rel chio8080.rel
sort -k1.8,1.9 -k1.4,1.7 hello.ihx > hello.hex
hex2bin -e com -c hello.hex
hex2bin v1.0.1, Copyright (C) 1999 Jacques Pelletier
Lowest address = 00000100
Highest address = 00000374
Build finished


Po nahratí na CP/M stroj a spustení COM súboru nám to naozaj vypíše Hello World!!
Niekomu by sa COM súbor zdal moc veľký. Je to spôsobené tým, že je tam prilinkovaná kompletná run-time knižnica crun8080lib. Táto obsahuje aj napríklad rutiny na násobenie, delenie, rotácie, switch príkazy a kopu ďalších, ktoré v Hello World nepoužívame.

Thursday, February 28, 2013

SCC1. Úvod do SmallC

Bol socializmus. Hrávali sa dve hymny. Československá a sovietska. V televízii boli dva programy. Amatérske rádio prinášalo články o splnených úlohách a zjazdoch KSČ. Na viedni bežal KITT. Vrchol techniky boli sedemmelódiové digitálky. Potom prišli 8-bity. V tuzexe sa dal kúpit Sinclair Spectrum. V predajniach občas mali Atari 800XL. Na stanici mladých technikov dostali dve PMDéčka. Programy sa nahrávali na kazety. Prvý program, ktorý som naťukal bol v Basicu na PMD85. Dodnes si pamätám na neuveriteľný pocit z toho, že to čo píšem na klávesnici vidím na obrazovke.

Nadšene som hltal príručku k PMD85 a učil sa príkazy Basic. V tej dobe zároveň vychádzal v Elektróne hi-tech seriál o programovaní v assembleri 8080. Nejakým spôsobom sa mi dostal do rúk program DDT602. Bol to celobrazovkový assembler a disassembler pre PMD85. Potom sa mi dostala do rúk aj kópia Monitor PMD85. Zase som mal čo študovať. V tej dobe som už počul o jazyku Pascal. Ale na PMD85 nič také nebolo. Po prechode na výšku, som už kontakt s 8-bitmi stratil úplne. Prišla doba PC, diskiet a harddiskov. A tak som sa nejako nikdy nestretol s CP/M a ani som netušil, že na 8080 existovali aj C kompilery.

Prešlo veľa rokov. Vlastne ich bolo asi 20. Pri browsovaní na mňa na full HD LCD jedného dňa vyskočil mne neznámy pojem SmallC. Ostal som dosť prekvapený, pretože sa jednalo o C kompiler pre 8-bitový procesor z počítačového praveku Intel 8080. Vždy som chcel spoznať ako funguje kompiler. Assembler 8080 som si ešte matne pamätal. Neváhal som a stiahol som si zdrojáky. Vznikali niekedy v lete 1980 !!!

SmallC nie je plnohodnotný kompiler C. Keď ho Roin Cain napísal podporoval iba while cykly a z typov len char a int. Ďalej jednorozmerné polia a pointre. Napriek tomu to bol veľmi silný nástroj. Ron na motorke odniesol zdrojáky vytlačené na papieri do redakcie Dr'Dobbs časopisu. Vyšli v júli 1980 v čísle 45. Ron sa radšej nepochválil, že C kompiler ktorý práve napísal je len jeho druhý program v C. Prvý bol obligátny Hello World. Aj tak mu nechceli veriť, že na tom výstupe z tlačiarne je C kompiler, pretože .... tam videli C zdrojový kód. V tej dobe sa jednalo o nevídanú vec. Kompiler ktorý prekladá sám seba.

Ron dal kód k dispozícii verejnosti a rýchlo vznikali varianty a rozšírenia jeho kompileru. Mne sa zapáčila verzia, ktorá bola portovaná na UNIX. Napísal ju Chris Lewis a pridal rozšírenia oproti verzii 2.0. Po jemných úpravách sa dala preložiť aj s gcc. Pri spustení vygenerovala pekný core :/ Tak som pátral ďalej a po úprave spracovania args a argv začala fungovať.

Wednesday, February 27, 2013

SCC2. SmallC - deklarácie premenných

SmallC je jednopriechodový kompiler. Nežartujem. Číta C kód z textového súboru alebo konzoly. Nepoužíva lex/yacc. Ron si napísal vlastný parser, lexer a generátor kódu. Na výstupe generuje asm kód. Tento je spracovávaný asemblerom. Ja som upravil SmallC tak, aby produkoval kód pre 5-priechodový ASXXXX. Nežartujem, naozaj je 5-priechoddový ;) Výsledkom ASLINK linkera je Intel Hex súbor.

Všetky knižky o kompileroch venujú veľa priestoru teórii. To by až tak nevadilo, keby častokrát úplne nevypúšťali časť venujúcu sa generovaniu kódu. A práve pohľad na generovaný kód pomôže pochopiť ako funguje kompiler. 8080 je tak trochu už 16-bit procesor. Má tri registrové páry zložené z dvojíc registrov: BC, DE a HL. Tieto registrové páry umožňujú 16 bitové operácie ako add, sub, lhld, shld, lxi. SmallC používa takzvaný primary/secondary register koncept. Používa len dva registrové páry. Primárny je HL, secondary je DE. BC sa nepoužíva.

SmallC má jednu vynikajúcu vlastnosť. Umožňuje interaktívnu prácu. Človek napíše riadok C kódu, stlačí ENTER a hneď dostane výstupný asm kód. Je to perfektné pre zoznámenie sa s kompilerom. Poďme si to vyskúšať. Tu je preložená verzia pre Windows. scc8080. Záujemci o linux verziu si zdrojový kód https://github.com/ncb85/SmallC-85 iste bez problémov skompilujú sami.

Otvoríme si prompt a spustíme kompiler: scc8080 -t
Flag -t hovorí, že vstupné riadky budú kopírované do výsledného asm kódu ako komentáre. Nezadali sme názov file, takže vstup aj výstup bude na konzole. Mali by sme vidieť takéto niečo:
; Small C 8080
; Coder (2.4,84/11/27)
; Front End (2.7,84/11/28)
; Front End for ASXXXX (2.8,13/01/20)
;program area SMALLC_GENERATED is RELOCATABLE
.area SMALLC_GENERATED (REL,CON)
.module SMALLC_GENERATED
.list (err, loc, bin, eqt, cyc, lin, src, lst, md)
.nlist (pag)
.area SMALLC_GENERATED (REL,CON,CSEG)
napíšem: int a; a stlačím CTRL-Z a ENTER čím pošlem EOF znak. Na niektorých OS je treba poslať znak CTRL-D. SmallC vidí koniec file EOF a preto už nečaká na ďalší riadok ale skončí. Mal by vypísať toto:
.area SMALLC_GENERATED_DATA (REL,CON,DSEG)
.globl a
a:
.dw #0

;0 error(s) in compilation
; literal pool:0
; global pool:1
; Macro pool:45
; .end

Vo výstupnom asm je label s názvom našej deklarovanej premennej a má rezervované miesto 2 bytes (DB - Define Byte, 8 bits. DW - Define Word, 16 bits) čo je správne, pretože a je int. A nielen to, má aj východziu hodnotu. Tá je nastavená na 0. SmallC sa postará o to, aby globálne premenné mali garantovanú hodnotu. Vhodné sú ďalšie experimenty, deklarácia viacerých premenných, rôznych typov (int, char, unsigned int, unsigned char).

Tuesday, February 26, 2013

SCC3. SmallC - krátky program

V minulom článku sme skúsili interaktívny režim SmallC. Teraz skúsme kratučký program a pozrieme sa na vygenerovaný kód. Spustime kompiler príkazom scc8080 -t
Zadajme:
int gla, glb = 3;
char msg[]="hello world", glc, *glpc;

method_a () {
   gla = glb;
   glc = 'x';
   glpc = msg;
}
Dostaneme:
; gla = glb;
lhld glb
shld gla
do primárneho registrového páru sa načítala hodnota na adrese glb a uložila sa na adresu gla
; glc = 'x';
lxi h,#120
mov a,l
sta glc
do HL sa načítalo 'x' čo je ASCII 120, všimnime si, že aj typ char sa načítava do registrového páru. Následne sa už správne uložila len hodnota z L do A a v poslednom kroku sa uložil A na adresu glc
; glpc = msg;
lxi h,msg
shld glpc
do HL sa načítala adresa začiatku poľa msg. V C sú stringy interpretované ako char array. Adresa poľa msg sa uložila na adresu glpc.

Na konci nasledujú deklarácie:
gla:
.dw #0
gla je int, má rezervované dva byte. Keďže nemá inicializačnú časť, bola mu defaultne daná hodnota 0
glb:
.dw #3
glb má hodnotu 3, presne ako má v inicializačnej časti
msg:
.db #104,#101,#108,#108,#111,#32,#119,#111,#114,#108
.db #100,#0
msg je pole bytov, inicializované na znaky hello world, ukončené 0.

Monday, February 25, 2013

SCC4. SmallC - funkcie

Jedna z najväčších výhod C jazyka sú lokálne premenné. Lokálne premenné nemajú miesto vo výslednej binárke, ale sa alokujú dynamicky. Počas behu programu, pri potrebe lokálnej premennej sa na zásobníku alokuje pamäť. Napríklad obyčajný PUSH posunie SP o dva byte. Na vzniknutom mieste môžeme ukladať hodnoty. Pri ukončení potreby lokálnej premennej sa urobí POP. Tým sa zásobník vráti do pôvodného stavu. Skúsme takýto kód
main() {
int la;
la = 5;
}
Začiatok metódy je označený labelom main:
;main() {
main:
Ďalej dostaneme:
;int a;
push b
;a=5;
lxi h,#0
dad sp
push h
lxi h,#5
pop d
call ccpint
Deklarácia premennej v metóde spôsobí okamžitý PUSH aby sa alokoval priestor. Ďalej nasleduje dvojica metód LXI a DAD ktoré nastavia do HL adresu premennej a.
Táto adresa je hneď uložená na stack. Toto je typické pre kód generovaný podľa primary/secondary register konceptu. Pretože nemáme viac registrov, každá hodnota sa musí hneď ukladať na stack aby sa uvoľnili jediné dva registre. Toto je na jednej strane jednoduchšie na implementáciu a aj výsledný asm kód je ľahko čitateľný. Na druhej strane je to veľmi neefektívne. Moderné procesory majú desiatky registrov a namiesto ukladania na stack sa alokujú ďalšie a ďalšie registre. Ale napísať túto časť kompileru (register alocation) patrí medzi najťažšie úlohy.
Ďalej nasleduje načítanie hodnoty 5 do HL a do DE sa popne adresa lokálnej premennej. A zavolá sa rutina z knižnice, ktorá uloží HL na adresu DE. Mimochodom tieto tri inštrukcie sa dajú nahradiť jedinou. Existuje aj nedokumentovaná inštrukcia 8085 s názvom SHLX. Prepínač -u v SmallC zapne generovanie kódu s nedokumentovanými inštrukciami. Pri zapnutí -u sa vo výpise obajví aj ďalšia nedokumentovaná inštruckia - LDSI.

Koniec metódy:
pop b
ret
Pop opraví SP a ret ukončí metódu.
Ako sa zmení kód ak budeme mať dve lokálne premenné ? Nechávam to na vlastné pokusy.

Sunday, February 24, 2013

SCC5. SmallC - príkazy

Príkazy sú prekvapivo jednoduché na implementáciu. Skúsme sa pozrieť na if. Zadajme:
main(){
    if (1) {
    } else {
    }
}
Dostaneme:
main:
;if (1) {
lxi h,#1
mov a,h
ora l
jz $2
;} else {
jmp $3
$2:
;}
$3:

A koniec mainu:
}
$1:
ret
Hmm je treba komentár ? Do primárneho registrového páru HL sa načítala hodnota výrazu a kombináciou mov a ora sa testuje na nulu. V C je všetko okrem nuly true. Medzi jz $2 a jmp $3 by išiel kód príkazov, ktoré sa majú vykonať pri splnenej podmienke. jmp $3 tam je kvôli tomu, aby sa nevykonal kód príkazov else vetve. Je zaujímavé pozrieť si, ako sa interpretujú ostatné príkazy ako case, while, for..

Saturday, February 23, 2013

SCC6. SmallC - pohľad do kódu

Videli sme aký jednoduchý je kód pre príkazy. Ich implementácia v kompileri tiež patrí medzi jednoduchšie časti. Toto je zjednodušený listing. Ukazuje akým spôsobom kompiler vygeneroval postupnosť inštrukcií z predchádzajúceho článku.
flab1 = getlabel ();
test (flab1, FALSE);
statement (NO);
if (!amatch ("else", 4)) {
  generate_label (flab1);
  return;
}
gen_jump (flab2 = getlabel ());
generate_label (flab1);
statement (NO);
generate_label (flab2);
Na prvom riadku sa inkrementne počítadlo labelov. Potom funkcia test vyhodnotí výraz a vygeneruje trojicu inštrukcií mov a,h a ora l a jz flab1. Nasleduje volanie statement, čo je spracovanie nula alebo viacerých príkazov, ktoré sa majú vykonať ak je podmienka splnená. Kompiler hneď generuje zodpovedajúce inštrukcie ak nájde príkazy. Potom sa testuje, či je prítomná else vetva. Ak nie (podmienka je vtedy splnená) tak sa vygeneruje sa flab1 pre funkciu test, ktorá už generovala jump na tento label. Return už len skončí. Ak ale máme aj vetvu if, tak sa generuje jump flab2, ktorý obskakuje vetvu if. Za týmto jumpom sa až vygenruje flab1 pre test zo začiatku metódy. A logicky sa volá statement, aby sa vygeneroval kód pre všetky príkazy v else vetve. Potom už konečne ide label2 pre obskakovací jump. Jednoduché že ?

Friday, February 22, 2013

SCC7. SmallC - symbol table

Napísať kompiler nie je jednoduché, pretože je to komplexná úloha. Je treba porozumieť viacerým častiam, ako napr. parser, generátor kódu, vyhodnocovanie výrazov a dátové štruktúry. Keď to zase na druhej strane všetko do seba zapadne, tak to konečne začne dávať zmysel. Ústredná dátová štruktúra každého kompileru je symbol table.
Symbol table je dátová štruktúra, kde kompiler zhromažďuje informácie o premenných (prípadne metódach, triedach..).
struct symbol_table {
  char name[NAMESIZE]; // symbol name
  int identity; // variable, array, pointer, function
  int type; // char, int
  int storage; // public, auto, extern, static
  int offset; // offset
}
Názov premennej je jasný. Identity hovorí, o čo sa jedná. Či o premennú, pole, pointer alebo funkciu. Type je zase jednoduchý, SmallC má len int a char. Hmm ja už som pridal unsigned int, unsigned char a struct :$
Storage súvisí s tým kde deklarujeme premennú, prípadne aky modifier použijeme. V C platí pravidlo, že premenné definované mimo metód sú globálne a statické. Premenné vnútri metód su lokálne a dočasné. Zanikajú po návrate z metódy. Celé to ešte komplikujú slová extern a static. Posledná položka má viac významov. U polí označuje veľkosť. U lokálnych premenných relatívnu pozíciu na stacku.
Je potrebné mať dve takéto tabuľky. Jednu pre globálne informácie. Tá sa plní a používa počas prekladu celého programu. Druhá je pre lokálne premenné. Vyprázdni sa vždy pred spracovaním funkcie.
A ako to funguje ? Pri deklarácii napr. int a; mimo metódy sa uloží do globálnej table patričná informácia. Potom ak v nejakej metóde nájdeme napr. výraz a=3; tak kompiler si nájde "a". Najskôr hľadá v lokálnej symbol table. Ak tam "a" nenájde hľadá v globálnej symbol table. Tým je garantované zakrývanie globálnych premenných lokálnymi s rovnakým názvom. A v našom prípade napr. nájde "a" v global symbol table. Takže vie, že je to int a má svoje miesto v pamäti na labeli a:
A preto vygeneruje inštrukcie: lxi a, 2 shld a

Thursday, February 21, 2013

SCC8. SmallC - ukladanie premenných

Keď som už ukladanie hodnôt do premenných v minulých blogoch načal, tak dám aj jeden príklad. Pozrieme sa ako sa narába s rôznymi typmi premenných. Vždy keď je treba uložiť hodnotu do nejakej premennej je volaná táto metóda.
store(LVALUE *lval) {
   if (lval->indirect == 0)
     gen_put_memory (lval->symbol);
   else
     gen_put_indirect (lval->indirect);
}
Je tvorená jedným if, ktorý sa pozrie, či narábame s pointerom, alebo s premennou. Presnejšie povedané, môže ísť o pointer. Ale nie o premennú nepriamo referencovanú pointerom. Podľa toho zavolá ďalšiu metódu. Ak sa jedná o premennú, je to jednoduché.
void gen_put_memory(SYMBOL *sym) {
   if ((sym->identity != POINTER) && (sym->type & CCHAR)) {
     output_line ("mov \ta,l");
     output_with_tab ("sta \t");
   } else {
     output_with_tab ("shld\t");
   }
   output_string (sym->name);
   newline ();
}
Tu je zase len jeden if. Ten sa pozerá, či je veľkosť byte alebo int. Aj pointer má veľkosť ako int. Ak je to teda byte, tak sa vygeneruje STA. Ešte predtým sa vygeneruje inštrukcia na presun low byte z primary registra do akumulárora. Ak sa jedná o dva byty, tak sa vygeneruje SHLD. Nakoniec sa vygeneruje label, pre inštruckie STA/SHLD. Label je miesto, kde má premenná rezervované miesto pomocou DB, DW.

Teraz sa pozrime na premenné nepriamo referencované pointerom.
void gen_put_indirect(char typeobj) {
   gen_pop ();
   if (typeobj & CCHAR) {
     output_line("mov \ta,l");
     output_line("stax\td");
   } else {
     if (uflag) {
       output_line("shlx");
     } else {
       gen_call("ccpint");
     }
   }
}
Ako prvý máme POP DE. Prečo ? Pretože v primárnom registri máme hodnotu premennej a na stacku máme jej adresu. Ciže hodnotu kam ukazuje pointer. Je to takéto jednoduché, pretože používame koncept primárneho registra. Potom sa zase rozhodujeme podľa hodnoty zo symbol table, či ide o byte alebo o int. V prípade byte je to klasika, vygenerujeme inštrukciu STAX D.

V prípade, že sa jedná o int sa ešte rozhodujeme, či chceme používať nedokumentovanú inštrukciu CPU 8085. SHLX uloží hodnotu v HL na adresu v DE. Ak sa chceme držať dokumentácie, prípadne generujeme kód pre CPU 8080, je to komplikovanejšie. Musíme volať maličký podprogram, ktorý urobí to isté ako jediná inštrukcia v 8085. Vyzerá takto.
; store int from HL into (DE)
ccpint: mov a,l
stax d
inx d
mov a,h
stax d
ret

Wednesday, February 20, 2013

SCC9. SmallC - výrazy

Toto je najťažšia časť kompileru. Nedá sa popísať jednoducho, preto len jednoduchý popis. Každý operátor má nejakú prioritu a asociativitu. Operátory sú rozdelené podľa priorít. Každej priorite zodpovedá jedna metóda hier1 až hier9. Implementované sú v súbore expr.c a primary.c. Každá funkcia hier1 až hier9 okamžite volá nasledovnú funkciu s vyšším číslom a teda aj vyššou prioritou. Výraz sa začne vyhodnocovať v expr() a potom sa rekurzívne zostúpi cez hier1, hier2 .. hier9 až do primary. Tu sa zistí aký operand máme, vyhľadá sa v symbol tabuľke a nastaví sa jeho typ a ukazateľ na tabuľku. Zároveň sa vráti 0 ak už bol v primary vygenerovaný kód ktorý načítal hodnotu do primárneho reg.páru. Až pri stúpaní (return) cez rekurzívne volania sa porovnáva aktuálny operátor s príslušným operátorom funkcie. Ak sa nájde zhoda, vyhodnocovanie sa začne na tejto úrovni a ako prvé sa zase zavolajú všetky funkcie s vyššou prioritou. Prečo ? Preto aby sa zabezpečilo prednostné a tým aj správne vyhodnotenie operátorov s vyššou prioritou.

A to je koniec. Najlepší spôsob ako sa zoznámiť s kompilerom, je študovať kód a skúšať ho zmeniť. SmallC ešte stále nie je plnohodnotný C kompiler, pretože nemá podporu typov long a float. Ďalej nepodporuje viac ako jednorozmerné polia a viac ako jednu úroveň indirekcie. Hoci sú to málo používané vlastnosti, ich implementácia je veľmi pekná a zaujímavá úloha.
Zopár linkov: http://compilers.iecc.com/crenshaw/ http://www.orangetide.com/smallc/book/chaps/toc1.htm http://www.amazon.com/The-Small-Handbook-James-Hendrix/dp/0835970124

Sunday, January 27, 2013

NCB85

Keď som sa rozhodol hrať so SmallC bolo mi jasné, že budem potrebovať niečo, kde by som mohol overiť vygenerovaný kód. Nazrel som do šuflíku so skromnými zásobami súčiastok. Mal som jeden SAB8085, dve 8155, dve SRAM 128kB, asi 5 EPROM 27128, dve 74LS373, nejaké 8255, jednu 8253 a kopec 7400, 7490A a podobne. Hneď som začal kresliť schému. Cieľ bol jednodoskáč, ktorý by využil čo najviac súčiastok zo šuflíka. Mal vedieť načítať Intel HEX file a spustiť ho. Nechcel som niečo na univerzálke, pretože som uvažoval nad dvoma kusmi.

Ešte nikdy som nenavrhoval vlastné PCB. Pre istotu som sa radšej obrátil na Jirku http://www.nostalcomp.cz/ncb85.php. Odhovoril ma od dynamickej rekonfigurácie pamäte, za čo som mu veľmi vďačný. Výsledkom je jednoduchý dekodér adries vyskladaný zo 7432. V rámci šetrenia počtu čipov som uprednostnil 8155 oproti kombinácii 8255 s 8253.

Software pre NCB85 je bohatší ako som plánoval. Ako prvý som napísal zamýšľaný jednoduchý shell s LOAD, JUMP, DUMP, SET, MOVE, COPY príkazmi. V dnešnej dobe PC už nemá asi veľký zmysel používať tool chain priamo na 8-bite. Oveľa pohodlnejšie je robiť cross development. Preto je takýto jednoduchý shell dostačujúci. Program v assembleri alebo C sa napíše a preloží na PC a spustí na NCB85.

Lenže jedného dňa som natrafil na MON85 od Dave Dunfielda. Po prečítaní dokumentácie mi bolo jasné, že Dave myslel dopredu. Portácia na NCB85 spočíva v úprave pár riadkov. MON85 umožňuje debugovanie a trasovanie programu, nastavenie breakpointov, prezeranie a modifikáciu obsahu registrov a pamäte. Ďalej výpis pamäte v HEX alebo v disassemblovanom formáte. Ovládanie portov pomocou príkazov IN a OUT. A aby toho nebolo málo, Roman Bórik tento monitor ešte vylepšil http://blog.borik.net/2012/01/upraveny-monitor-pre-ncb85.html. Roman ale neostal len pri MON85. Ako skúsený CP/M guru, preportoval CP/M na NCB85.

Ďalšia stránka kde je NCB85 popísané je u Martina na 8-bity.cz http://www.8bity.cz/2011/ncb85-single-board-computer/