Překladače programovacího jazyka C pro historické osmibitové mikroprocesory (2)

29. 9. 2022
Doba čtení: 39 minut

Sdílet

 Autor: Michal Tauchman, podle licence: CC BY-SA 4.0
Ve druhém článku o překladači cc65 si řekneme, jak můžeme využít některé jeho speciální vlastnosti, například možnost mixovat kód psaný v jazyku C s assemblerem (na úrovni zdrojového kódu) nebo použití pseudoproměnných.

Obsah

1. Dokončení popisu překladače jazyka C pro osmibitové mikroprocesory MOS 6502: cc65

2. Volba, do jaké paměťové oblasti se mají uložit lokální proměnné

3. Vliv klíčového slova register na způsob uložení lokálních proměnných

4. Přímý přístup k akumulátoru s využitím identifikátoru __A_

5. Přístup k registrovému páru A + X s využitím identifikátoru __AX__

6. Přístup k 32bitové hodnotě uložené ve dvou buňkách RAM a v registrovém páru A + X

7. Rozdíl mezi chováním operátoru pro pre-inkrementaci a post-inkrementaci

8. Součet celých čísel bez znaménka a se znaménkem

9. Modifikace bloku paměti s využitím ukazatele a offsetu

10. Přímý zápis instrukcí v assembleru do céčkového kódu

11. Použití reference na lokální proměnnou v assembleru

12. Nepatrně složitější příklad: ručně optimalizovaný součet dvou proměnných

13. Přístup ke globálním proměnným z assembleru

14. Bitové posuny a další operace prováděné s akumulátorem

15. Násobení a dělení dvěma popř. mocninou dvou

16. cc65 nakonec není tak hloupý – násobení třemi a pěti

17. Násobení větší konstantou

18. Upravený soubor Makefile

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Dokončení popisu překladače jazyka C pro osmibitové mikroprocesory MOS 6502: cc65

Na úvodní článek o překladačích jazyka C určených pro různé osmibitové procesory dnes navážeme. Připomeňme si, že jsme se zabývali překladem zdrojových kódů napsaných v céčku do assembleru (a posléze i strojového kódu) osmibitových mikroprocesorů MOS 6502, které byly použity například v domácích počítačích Atari a Commodore, v osobních mikropočítačích Apple, v herních konzolích Atari 2600 (zjednodušený MOS 6507), NES atd. Jednalo se tedy o populární mikroprocesor, pro nějž vzniklo hned několik překladačů jazyka C. Nás však zajímá především cross překladač cc65, který umožňuje překlad zdrojových kódů na PC s přenosem výsledného strojového kódu na zvolenou osmibitovou architekturu. Cross překladač cc65 je poměrně populární, ovšem neprovádí žádné složitější optimalizace a pro jeho úspěšné použití je nutné znát jeho limity a různá temná zákoutí – v opačném případě nebude produkovaný kód kvalitní.

Poznámka: na rozdíl od moderních překladačů určených pro (taktéž) moderní architektury mikroprocesorů je v případě cc65 vždy možné bez většího úsilí tento překladač překonat a naprogramovat optimalizovaný kód přímo v assembleru. V případě moderních architektur je to samozřejmě mnohem složitější (a většinou taktéž zbytečné, což o MOS 6502 neplatí – zde je znalost assembleru takřka nutností).

Na úvod se podívejme na to, jakým způsobem se přeloží prázdná funkce main, kterou jsme navíc (poněkud v rozporu se standardem céčka) nadeklarovali bez parametrů a bez návratové hodnoty:

void main(void)
{
}

Překlad do assembleru provedeme příkazem:

$ cc65 -T -Cl -O -o empty_main.asm empty_main.c

Význam jednotlivých přepínačů:

  • -T zajistí, že se do kódu v assembleru v poznámkách vloží i původní zdrojový kód, takže bude zřejmé, jaké strojové instrukce odpovídají danému řádku v céčku.
  • -Cl povolí použití statických lokálních proměnných, což vede ke kratšímu a současně i rychlejšímu kódu, protože MOS 6502 neumí (dobře) adresovat operandy na zásobníku.
  • -O povolí základní optimalizace prováděně překladačem. Bez tohoto přepínače nemá smysl cc65 vůbec používat (skutečně!).
  • -o je volba určující jméno výsledného souboru s assemblerem.

Výsledkem bude soubor nazvaný empty_main.asm, jenž bude vypadat následovně:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; }
;
        rts
 
.endproc

Povšimněte si, že překlad je alespoň v tomto případě velmi úsporný – samotný kód obsahuje pouze jedinou instrukci rts neboli návrat z podprogramu (subrutiny).

2. Volba, do jaké paměťové oblasti se mají uložit lokální proměnné

Nyní do původně prázdné funkce main přidáme deklaraci a inicializaci lokální proměnné:

void main(void)
{
    unsigned char x = 42;
}

Na moderních mikroprocesorových architekturách by se tato proměnná uložila do zásobníkového rámce (stack frame), což je ovšem na MOS 6502 obtížná operace (zásobníkové rámce by se musely relativně složitě emulovat). Proto existuje možnost uložit tuto proměnnou do segmentu BSS, který není obsažen v objektovém kódu – jedná se jen o pojmenování paměťové oblasti:

.segment        "BSS"
 
L0002:
        .res    1,$00

Přístup k této paměťové oblasti vyžaduje „dlouhé“ adresování, tedy použití 16bitové adresy v instrukci:

        lda     #$2A
        sta     L0002
Poznámka: oblast může být využita více funkcemi – proměnné lze přepisovat.

Úplný kód napsaný v assembleru vzniklý překladem původního céčkového kódu vypadá následovně:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
 
.segment        "CODE"
 
;
; unsigned char x = 42;
;
        lda     #$2A
        sta     L0002
;
; }
;
        rts
 
.endproc

Alternativně je možné přepínačem -Or zajistit, že se původně lokální nestatické proměnné uloží do nulté stránky paměti popř. přímo do pracovního registru A (akumulátoru):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; unsigned char x = 42;
;
        lda     #$2A
        jsr     pusha
;
; }
;
        jmp     incsp1
 
.endproc
Poznámka: zde se volá podprogram (subrutina) určená pro emulaci zásobníkového rámce, resp. přesněji pro uložení obsahu akumulátoru A do emulovaného zásobníkového rámce.

3. Vliv klíčového slova register na způsob uložení lokálních proměnných

Programovací jazyk C umožňuje v deklaraci proměnných (resp. lokálních proměnných) použít klíčové slovo register, kterým se překladači říká, že daná proměnná je využívána často (například v programové smyčce) a bylo by ji tedy vhodné umístit přímo do registru. Co však toto klíčové slovo říká překladači cc65, který musí pracovat pouze se třemi registry? Můžeme si to relativně snadno vyzkoušet:

void main(void)
{
    register unsigned char x = 42;
}

Překlad provedeme dvakrát, nejdříve s přepínači -Cl a -O:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
 
.segment        "CODE"
 
;
; register unsigned char x = 42;
;
        lda     #$2A
        sta     L0002
;
; }
;
        rts
 
.endproc
Poznámka: v tomto případě je „lokální proměnná, kterou lze uložit do registru“ ve skutečnosti opět umístěna do segmentu BSS, tedy stejně jako neinicializovaná globální proměnná!

Druhý překlad je proveden s přepínači -Or a -O:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; register unsigned char x = 42;
;
        lda     regbank+5
        jsr     pusha
        lda     #$2A
        sta     regbank+5
;
; }
;
        ldy     #$00
        lda     (sp),y
        sta     regbank+5
        jmp     incsp1
 
.endproc

Nyní je stejná proměnná umístěna na nultou stránku paměti, takže přístup k ní bude rychlejší a i výsledný kód může být kratší.

Podívejme se, jak rozdíl v obou generovaných assemblerech zvýrazní diff:

; -------------------------------------------   ; -------------------------------------------
; void __near__ main (void)                     ; void __near__ main (void)
; -------------------------------------------   ; -------------------------------------------
 
.segment        "CODE"                          .segment        "CODE"
 
.proc   _main: near                             .proc   _main: near
 
.segment        "BSS"                         <
                                              <
L0002:                                        <
        .res    1,$00                         <
                                              <
.segment        "CODE"                          .segment        "CODE"
 
;                                               ;
; register unsigned char x = 42;                ; register unsigned char x = 42;
;                                               ;
                                              >         lda     regbank+5
                                              >         jsr     pusha
        lda     #$2A                                    lda     #$2A
        sta     L0002                         |         sta     regbank+5
;                                               ;
; }                                             ; }
;                                               ;
        rts                                   |         ldy     #$00
                                              >         lda     (sp),y
                                              >         sta     regbank+5
                                              >         jmp     incsp1
 
.endproc                                        .endproc
Poznámka: druhý kód je sice delší, ale při dalších manipulacích s proměnnou výhodnější.

4. Přímý přístup k akumulátoru s využitím identifikátoru __A__

Překladač cc65 umožňuje přímou manipulaci s akumulátorem s využitím identifikátoru __A__. Z pohledu programátora se jedná o proměnnou typu unsigned char. Podívejme se na jednoduchý příklad manipulace s akumulátorem:

void main(void)
{
    __A__ = 42;
 
    ++__A__;
}

Po překladu do assembleru uvidíme, že výsledkem je do značné míry optimální kód – viz zvýrazněné instrukce:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; ++__A__;
;
        clc
        adc     #$01
;
; }
;
        rts
 
.endproc

5. Přístup k registrovému páru A + X s využitím identifikátoru __AX__

V předchozí kapitole jsme si ukázali, jak lze velmi snadno využít __A__ ve funkci velmi rychle přístupné proměnné typu unsigned char (jejíž obsah je ovšem později přepsán dalšími příkazy). Podobně lze použít identifikátor __AX__, který představuje registrový pár A + X, ve funkci lokální proměnné typu unsigned short int, tedy 16bitového celého čísla bez znaménka:

void main(void)
{
    __AX__ = 0x1234;
 
    ++__AX__;
}

Podívejme se nyní na vygenerovaný kód, který je velmi zajímavý. Povšimněte si, že registr X obsahuje horní bajt, zatímco akumulátor A dolní bajt šestnáctibitové hodnoty:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __AX__ = 0x1234;
;
        ldx     #$12
        lda     #$34
;
; ++__AX__;
;
        jsr     incax1
;
; }
;
        rts
 
.endproc

Zajímavé je, že při zapnutí všech optimalizací se ++__AX__; přeloží takovým způsobem, že se zvyšuje pouze hodnota akumulátoru, což je chyba překladače (překlad bude stejný, i když použijeme například konstantu 0×00ff):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __AX__ = 0x1234;
;
        lda     #$34
;
; ++__AX__;
;
        clc
        adc     #$01
;
; }
;
        rts
 
.endproc

6. Přístup k 32bitové hodnotě uložené ve dvou buňkách RAM a v registrovém páru A + X

Pokračujme dále v popisu speciálních vlastností překladače céčka cc65. V některých případech je nutné pracovat s 32bitovými hodnotami. To již pro osmibitový mikroprocesor MOS 6502 vybavený pouhými třemi osmibitovými registry představuje poměrně zásadní problém. Překladač cc65 sice programátorům nabízí pseudoproměnnou nazvanou __EAX__, která je 32bitová, ovšem interně nemůže být tato hodnota uložena v registrech mikroprocesoru (alespoň ne celá). Proto cc65 přistupuje ke kompromisu – horních šestnáct bitů je uloženo v nulté stránce paměti a dolních šestnáct bitů v nám již známé dvojici registrů X a A (Y se používá při adresování, předávání hodnot interním subrutinám atd.):

void main(void)
{
    __EAX__ = 0x12345678;
 
    ++__EAX__;
}

Zajímavý bude překlad tohoto céčkového kódu do assembleru:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __EAX__ = 0x12345678;
;
        ldx     #$56
        lda     #$34
        sta     sreg
        lda     #$12
        sta     sreg+1
        lda     #$78
;
; ++__EAX__;
;
        ldy     #$01
        jsr     inceaxy
;
; }
;
        rts
 
.endproc
Poznámka: jasně patrný je způsob rozložení 32bitového slova na čtyři bajty (horní dva bajty do paměťových oblastí sreg a sreg+1). Taktéž můžeme vidět, že registr Y se používá interně překladačem, zde konkrétně pro předání konstanty do subrutiny.

V tomto případě žádné optimalizace nepomůžou – optimalizován je jen poslední skok do subrutiny jsr následovaný návratem ze subrutiny rts za běžný skok jmp:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __EAX__ = 0x12345678;
;
        ldx     #$56
        lda     #$34
        sta     sreg
        lda     #$12
        sta     sreg+1
        lda     #$78
;
; ++__EAX__;
;
        ldy     #$01
        jmp     inceaxy
 
.endproc

7. Rozdíl mezi chováním operátoru pro pre-inkrementaci a post-inkrementaci

Na stránce cc65 coding hints jsou uvedeny některé užitečné tipy pro práci s překladačem cc65. Mj. se zde dozvíme, že je vhodnější používat prefixový operátor ++ (resp. –) a nikoli postfixovou variantu stejného operátoru. Teoreticky – pokud výsledek nijak nepoužijeme (tedy zaměříme se jen na vedlejší efekt tohoto operátoru) by měl být přeložený kód totožný, ovšem v případě cc65 tomu tak není. Snadno si to můžeme otestovat na těchto dvou příkladech, které oba de facto provádí stejnou operaci – zvýšení hodnoty interní proměnné x o jedničku:

void main(void)
{
    register unsigned int x = 42;
    x++;
}
void main(void)
{
    register unsigned int x = 42;
    ++x;
}

Výsledkem překladu prvního příkladu s x++ je kód, v němž jsou pro výpočet použity registry A a X, přičemž se testuje přetečení do vyššího bajtu (instrukce bcc):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    2,$00
 
.segment        "CODE"
 
;
; register unsigned int x = 42;
;
        ldx     #$00
        lda     #$2A
        sta     L0002
        stx     L0002+1
;
; x++;
;
        clc
        adc     #$01
        bcc     L0005
        inx
L0005:  sta     L0002
        stx     L0002+1
;
; }
;
        rts
 
.endproc

Při zapnutí optimalizací bude výsledný kód vypadat jinak. Až na odlišný způsob uložení proměnné se ovšem stále provádí stejný výpočet s podmíněným skokem:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; register unsigned int x = 42;
;
        lda     #$2A
        jsr     pusha0
;
; x++;
;
        ldy     #$01
        lda     (sp),y
        tax
        dey
        lda     (sp),y
        clc
        adc     #$01
        bcc     L0004
        inx
L0004:  jsr     stax0sp
;
; }
;
        jmp     incsp2
 
.endproc

Výsledek překladu příkladu s ++x vypadá odlišně – celý výpočet je proveden s hodnotou uloženou v operační paměti a nikoli v pracovních registrech, a to s využitím pouhých třech instrukcí (ve výpisu jsou zvýrazněny):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    2,$00
 
.segment        "CODE"
 
;
; register unsigned int x = 42;
;
        ldx     #$00
        lda     #$2A
        sta     L0002
        stx     L0002+1
;
; ++x;
;
        inc     L0002
        bne     L0005
        inc     L0002+1
;
; }
;
L0005:  rts
 
.endproc

Po zapnutí optimalizací se paradoxně volá podprogram pro součet hodnot:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; register unsigned int x = 42;
;
        lda     #$2A
        jsr     pusha0
;
; ++x;
;
        lda     #$01
        jsr     addeq0sp
;
; }
;
        jmp     incsp2
 
.endproc

8. Součet celých čísel bez znaménka a se znaménkem

Mikroprocesor MOS 6502 sice dokáže do určité míry pracovat s hodnotami se znaménkem (signed char, signed int atd.), ovšem výsledný kód bude poměrně komplikovaný. Ostatně můžeme se o tom přesvědčit sami při porovnání assembleru vygenerovaného pro následující dva céčkové zdrojové kódy, které se liší „jen“ rozdílem mezi signed a unsigned (což na moderních mikroprocesorech není vůbec problematické).

Operace s hodnotami bez znaménka:

void main(void)
{
    register unsigned char a;
    register unsigned char b;
    register unsigned char c;
    a = 10;
    b = 20;
    c = a + b;
}

Operace s hodnotami se znaménkem:

void main(void)
{
    register signed char a;
    register signed char b;
    register signed char c;
    a = 10;
    b = 20;
    c = a + b;
}

První příklad, tj. součet dvou hodnot bez znaménka, se přeloží přímočaře (i když ruční optimalizace jsou stále na místě):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
L0003:
        .res    1,$00
L0004:
        .res    1,$00
 
.segment        "CODE"
 
;
; a = 10;
;
        lda     #$0A
        sta     L0002
;
; b = 20;
;
        lda     #$14
        sta     L0003
;
; c = a + b;
;
        lda     L0002
        clc
        adc     L0003
        sta     L0004
;
; }
;
        rts
 
.endproc

Ovšem u druhého příkladu, tj. při součtu dvou hodnot se znaménkem, je již vygenerovaný kód dosti komplikovaný a vyžaduje dvojici podmíněných skoků bpl (skok, pokud je hodnota kladná):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
L0003:
        .res    1,$00
L0004:
        .res    1,$00
 
.segment        "CODE"
 
;
; a = 10;
;
        lda     #$0A
        sta     L0002
;
; b = 20;
;
        lda     #$14
        sta     L0003
;
; c = a + b;
;
        ldx     #$00
        lda     L0002
        bpl     L000B
        dex
L000B:  sta     ptr1
        stx     ptr1+1
        ldx     #$00
        lda     L0003
        bpl     L000C
        dex
L000C:  clc
        adc     ptr1
        pha
        txa
        adc     ptr1+1
        pla
        cmp     #$80
        sta     L0004
;
; }
;
        rts
 
.endproc
Poznámka: pokud se tedy můžete hodnotám se znaménkem vyhnout, je to vždy žádoucí.

9. Modifikace bloku paměti s využitím ukazatele a offsetu

Minule jsme si ukázali několik způsobů implementace funkce nazvané memset8, která dokáže vyplnit určitý blok paměti (menší než 256 bajtů) zadanou hodnotou. Používali jsme přitom přístup do bloku s využitím operátoru [], tj. chovali jsme se k celému bloku jako k poli:

#include <stdint.h>
 
void memset8(uint8_t * dest, const uint8_t c, const uint8_t n)
{
    register uint8_t i;
 
    for (i = 0; i < n; i++) {
        dest[i] = c;
    }
}
 
int main(void)
{
    uint8_t *dest = (uint8_t *) 0x0600;
    uint8_t bytes = 0xff;
    uint8_t fill = 0x00;
    memset8(dest, fill, bytes);
 
    return 0;
}

Výsledný kód vypadat po optimalizaci takto:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _memset8
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ memset8 (__near__ unsigned char *, const unsigned char, const unsigned char)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _memset8: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
 
.segment        "CODE"
 
;
; {
;
        jsr     pusha
;
; for (i = 0; i < n; i++) {
;
        lda     #$00
        sta     L0002
L001B:  lda     L0002
        ldy     #$00
        cmp     (sp),y
        bcs     L0004
;
; dest[i] = c;
;
        ldy     #$03
        jsr     ldaxysp
        clc
        adc     L0002
        bcc     L001A
        inx
L001A:  sta     ptr1
        stx     ptr1+1
        ldy     #$01
        lda     (sp),y
        dey
        sta     (ptr1),y
;
; for (i = 0; i < n; i++) {
;
        inc     L0002
        jmp     L001B
;
; }
;
L0004:  jmp     incsp4
 
.endproc
 
; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L000F:
        .res    2,$00
L0011:
        .res    1,$00
L0013:
        .res    1,$00
 
.segment        "CODE"
 
;
; uint8_t *dest = (uint8_t *) 0x0600;
;
        ldx     #$06
        lda     #$00
        sta     L000F
        stx     L000F+1
;
; uint8_t bytes = 0xff;
;
        lda     #$FF
        sta     L0011
;
; uint8_t fill = 0x00;
;
        lda     #$00
        sta     L0013
;
; memset8(dest, fill, bytes);
;
        lda     L000F
        ldx     L000F+1
        jsr     pushax
        lda     L0013
        jsr     pusha
        lda     L0011
        jsr     _memset8
;
; return 0;
;
        ldx     #$00
        txa
;
; }
;
        rts
 
.endproc

Kdysi, zhruba v dobách vlády DOSu (a nastupující vlády Linuxu) se tvrdilo, že indexování prvků pole operátorem [] je pomalé a že je lepší ho nahradit za operaci *(bázová_adresa+offset), tedy za přímou manipulaci s pamětí (nebo ještě lépe kompletní eliminací offsetu a posunem bázové adresy). Jak je tomu ovšem v případě překladače cc65? Pokusme se předchozí příklad upravit tak, aby se používalo adresování bajtů v bloku paměti pomocí bázové adresy a offsetu:

#include <stdint.h>
 
void memset8(uint8_t * dest, const uint8_t c, const uint8_t n)
{
    register uint8_t i;
 
    for (i = 0; i < n; i++) {
        *(dest+i) = c;
    }
}
 
int main(void)
{
    uint8_t *dest = (uint8_t *) 0x0600;
    uint8_t bytes = 0xff;
    uint8_t fill = 0x00;
    memset8(dest, fill, bytes);
 
    return 0;
}

Zajímavé je, že tento příklad se přeloží do zcela totožné sekvence instrukcí, jako příklad předchozí, takže naše „optimalizace“ na úrovni céčkového kódu v tomto případě postrádá smysl:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _memset8
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ memset8 (__near__ unsigned char *, const unsigned char, const unsigned char)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _memset8: near
 
.segment        "BSS"
 
L0002:
        .res    1,$00
 
.segment        "CODE"
 
;
; {
;
        jsr     pusha
;
; for (i = 0; i < n; i++) {
;
        lda     #$00
        sta     L0002
L001B:  lda     L0002
        ldy     #$00
        cmp     (sp),y
        bcs     L0004
;
; *(dest+i) = c;
;
        ldy     #$03
        jsr     ldaxysp
        clc
        adc     L0002
        bcc     L001A
        inx
L001A:  sta     ptr1
        stx     ptr1+1
        ldy     #$01
        lda     (sp),y
        dey
        sta     (ptr1),y
;
; for (i = 0; i < n; i++) {
;
        inc     L0002
        jmp     L001B
;
; }
;
L0004:  jmp     incsp4
 
.endproc
 
; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "BSS"
 
L000F:
        .res    2,$00
L0011:
        .res    1,$00
L0013:
        .res    1,$00
 
.segment        "CODE"
 
;
; uint8_t *dest = (uint8_t *) 0x0600;
;
        ldx     #$06
        lda     #$00
        sta     L000F
        stx     L000F+1
;
; uint8_t bytes = 0xff;
;
        lda     #$FF
        sta     L0011
;
; uint8_t fill = 0x00;
;
        lda     #$00
        sta     L0013
;
; memset8(dest, fill, bytes);
;
        lda     L000F
        ldx     L000F+1
        jsr     pushax
        lda     L0013
        jsr     pusha
        lda     L0011
        jsr     _memset8
;
; return 0;
;
        ldx     #$00
        txa
;
; }
;
        rts
 
.endproc

10. Přímý zápis instrukcí v assembleru do céčkového kódu

V rámci předchozích kapitol jsme si ukázali některé problematické rysy překladače cc65. V případě, že narazíme na neoptimální kód, lze situaci zachránit, a to přímým zápisem instrukcí v assembleru. Pro tento účel se používá „funkce“ nazvaná __asm__, které se v řetězci předá jak vlastní instrukce, tak i popř. proměnná, s níž má instrukce operovat. Podívejme se na ten nejjednodušší možný případ, tj. na vygenerování sekvence instrukcí, které nepracují s žádnou lokální ani globální proměnnou:

void main(void)
{
    __asm__ ("lda #1");
    __asm__ ("clc");
    __asm__ ("adc #2");
}

Sekvence instrukcí, které jsme zapsali do __asm__, se objeví ve výsledném assemblerovském kódu:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __asm__ ("lda #1");
;
        lda     #1
;
; __asm__ ("clc");
;
        clc
;
; __asm__ ("adc #2");
;
        adc     #2
;
; }
;
        rts
 
.endproc

11. Použití reference na lokální proměnnou v assembleru

Jak jsme si již řekli v předchozí kapitole, je možné pseudofunkci __asm__ předat i proměnnou, s níž má instrukce pracovat. Například v následujícím příkladu vypočteme součet 1+2 a uložíme jeho výsledek do lokální proměnné a:

void main(void)
{
    register unsigned char a;
 
    __asm__ ("lda #1");
    __asm__ ("clc");
    __asm__ ("adc #2");
    __asm__ ("sta %v", a);
}
Poznámka: zde je patrné, že __asm__ není a ani nemůže být funkce, ale spíše makro, protože proměnnou a nevyhodnocuje tak, jak by tomu bylo u klasické céčkové funkce.

V assemblerovském výpisu si povšimněte zejména toho, jakým způsobem je přeložena poslední instrukce – tedy zápis výsledku do lokální proměnné a:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; register unsigned char a;
;
        lda     regbank+5
        jsr     pusha
;
; __asm__ ("lda #1");
;
        lda     #1
;
; __asm__ ("clc");
;
        clc
;
; __asm__ ("adc #2");
;
        adc     #2
;
; __asm__ ("sta %v", a);
;
        sta     regbank+5
;
; }
;
        ldy     #$00
        lda     (sp),y
        sta     regbank+5
        jmp     incsp1
 
.endproc

12. Nepatrně složitější příklad: ručně optimalizovaný součet dvou proměnných

Se znalostí toho, jak lze assemblerovským instrukcím předávat proměnné (tj. jejich jména, nikoli hodnoty nebo reference), můžeme součet c=a+b realizovat optimalizovaným assemblerem, a to například takto:

void main(void)
{
    register unsigned char a;
    register unsigned char b;
    register unsigned char c;
 
 
    __asm__ ("lda %v", a);
    __asm__ ("clc");
    __asm__ ("adc %v", b);
    __asm__ ("sta %v", c);
}

Ve vygenerovaném assembleru je ona čtveřice instrukcí zvýrazněna, aby bylo zřejmé, že se skutečně pracuje s adresami proměnných:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; register unsigned char a;
;
        lda     regbank+5
        jsr     pusha
;
; register unsigned char b;
;
        lda     regbank+4
        jsr     pusha
;
; register unsigned char c;
;
        lda     regbank+3
        jsr     pusha
;
; __asm__ ("lda %v", a);
;
        lda     regbank+5
;
; __asm__ ("clc");
;
        clc
;
; __asm__ ("adc %v", b);
;
        adc     regbank+4
;
; __asm__ ("sta %v", c);
;
        sta     regbank+3
;
; }
;
        ldy     #$00
L000A:  lda     (sp),y
        sta     regbank+3,y
        iny
        cpy     #$03
        bne     L000A
        jmp     incsp3
 
.endproc

13. Přístup ke globálním proměnným z assembleru

S naší znalostí toho, jakým způsobem je možné kombinovat céčko a assembler, se můžeme pustit i do nepatrně „složitějšího“ příkladu. Například se můžeme pokusit o realizaci součtu dvou globálních proměnných, přičemž výsledek bude taktéž uložen do globální proměnné. Samotná realizace takového programu, v němž budeme přímo generovat instrukce v assembleru, může vypadat následovně:

unsigned char a;
unsigned char b;
unsigned char c;
 
void main(void)
{
    __asm__ ("lda %v", a);
    __asm__ ("clc");
    __asm__ ("adc %v", b);
    __asm__ ("sta %v", c);
}
Poznámka: pro jistotu je opět nutné před vlastním součtem vynulovat příznak carry, protože mikroprocesor MOS 6502 má pouze instrukci adc a nikoli add (bez carry).

Tento program se přeloží následujícím způsobem (zvýrazněny jsou bloky s globálními proměnnými i dotčená čtveřice instrukcí):

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _a
        .export         _b
        .export         _c
        .export         _main
 
.segment        "BSS"
 
_a:
        .res    1,$00
_b:
        .res    1,$00
_c:
        .res    1,$00
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __asm__ ("lda %v", a);
;
        lda     _a
;
; __asm__ ("clc");
;
        clc
;
; __asm__ ("adc %v", b);
;
        adc     _b
;
; __asm__ ("sta %v", c);konkrétní 
;
        sta     _c
;
; }
;
        rts
 
.endproc

Zajímavé bude vysledovat, jak vlastně budou instrukce, které ke globálním proměnným přistupují, přeloženy do strojového kódu:

$ ca65 --listing assembly4_list.asm assembly4.asm

S následujícím výsledkem:

000000r 1               ;
000000r 1               ; __asm__ ("lda %v", a);
000000r 1               ;
000000r 1  AD rr rr             lda     _a
000003r 1               ;
000003r 1               ; __asm__ ("clc");
000003r 1               ;
000003r 1  18                   clc
000004r 1               ;
000004r 1               ; __asm__ ("adc %v", b);
000004r 1               ;
000004r 1  6D rr rr             adc     _b
000007r 1               ;
000007r 1               ; __asm__ ("sta %v", c);
000007r 1               ;
000007r 1  8D rr rr             sta     _c
00000Ar 1               ;
00000Ar 1               ; }
00000Ar 1               ;
00000Ar 1  60                   rts
Poznámka: povšimněte si, že se používají „dlouhé“ adresy (tedy instrukce s celkovou délkou tří bajtů), což není ideální ve chvíli, kdy se podobné globální proměnné používají velmi často – tehdy je lepší použít nultou stránku paměti.

14. Bitové posuny a další operace prováděné s akumulátorem

Vyzkoušejme si nyní, jakým způsobem se přeloží céčkovské příkazy, které manipulují s akumulátorem s využitím základních céčkovských operátorů. Začneme operací __A__ += 1, protože způsob překladu operátoru ++ (jak v prefixové, tak i v postfixové variantě) jsme si již ukázali v rámci předchozích kapitol:

void main(void)
{
    __A__ = 42;
 
    __A__ += 1;
}

Překlad je v tomto případě (ovšem pouze při zapnutí optimalizací) přímočarý a odpovídá tomu, co bychom sami zapsali do assembleru:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ += 1;
;
        clc
        adc     #$01
;
; }
;
        rts
 
.endproc

Podobně si můžeme vyzkoušet překlad céčkovského kódu, v němž se obsah akumulátoru posune doleva:

void main(void)
{
    __A__ = 42;
 
    __A__ <<= 1;
}

Výsledek je v tomto případě opět optimální:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ <<= 1;
;
        asl     a
;
; }
;
        rts
 
.endproc

A pro úplnost si ukažme i posun doprava, tedy vlastně dělení dvěma (bez znaménka!):

void main(void)
{
    __A__ = 42;
 
    __A__ >>= 1;
}

S výsledkem:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ >>= 1;
;
        lsr     a
;
; }
;
        rts
 
.endproc

Pro úplnost doplňme posun o dva bity doleva i doprava:

void main(void)
{
    __A__ = 42;
 
    __A__ >>= 2;
    __A__ <<= 2;
}

Což se přeloží do kódu:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ >>= 2;
;
        lsr     a
        lsr     a
;
; __A__ <<= 2;
;
        asl     a
        asl     a
;
; }
;
        rts
 
.endproc
 

15. Násobení a dělení dvěma popř. mocninou dvou

V případě, že budeme obsah proměnné (a ještě lépe obsah akumulátoru) násobit dvěma či nějakou vyšší mocninou dvojky, bude tato operace překladačem cc65 převedena na instrukci pro bitový posun doleva – ovšem pouze v případě, že povolíme optimalizace (bez těch není možné cc65 reálně používat). Můžeme si to ostatně velmi snadno otestovat na tomto příkladu:

void main(void)
{
    __A__ = 42;
 
    __A__ *= 2;
}

Po překladu je zřejmé, že se skutečně použila instrukce ASL určená pro bitový posun doleva:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ *= 2;
;
        asl     a
;
; }
;
        rts
 
.endproc

Naopak při dělení dvěma nebo dělení vyšší mocninou dvojky se použije bitový posun doprava. Opět si to otestujeme:

void main(void)
{
    __A__ = 42;
 
    __A__ /= 2;
}

Výsledek bude po překladu vypadat následovně:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ /= 2;
;
        lsr     a
;
; }
;
        rts
 
.endproc

Při násobení čtyřmi se posun provede dvakrát, což je taktéž korektní:

void main(void)
{
    __A__ = 42;
 
    __A__ *= 4;
}

S výsledkem, v němž můžeme vidět dvojí použití instrukce ASL:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ *= 4;
;
        asl     a
        asl     a
;
; }
;
        rts
 
.endproc
 

Pozor ovšem na situaci, kdy jsou optimalizace zakázány. V takovém případě dostaneme zcela neoptimální kód:

        ldx     #$00
        lda     #$2A
        jsr     shlax1
        rts

16. cc65 nakonec není tak hloupý – násobení třemi a pěti

V předchozích kapitolách jsme si uvedli poměrně velké množství příkladů, z nichž je zřejmé, že překladač cc65 nedokáže provádět mnohé optimalizace, na které jsme zvyklí z moderních překladačů určených pro moderní mikroprocesory. Na druhou stranu ovšem není cc65 úplně hloupý, protože dokáže optimalizovat alespoň násobení určitými konstantami. Ukázali jsme si, jak je rozpoznáno násobení mocninou dvojky, ovšem cc65 dokáže nahradit i násobení dalšími konstantami optimalizovaným kódem. Začněme vynásobením akumulátoru třemi (násobíme akumulátor, aby výsledný kód neobsahoval zbytečné paměťové operace):

void main(void)
{
    __A__ = 42;
 
    __A__ *= 3;
}

V tomto případě je násobení nahrazeno posunem doleva (tedy vynásobením dvěma) a součtem s původní hodnotou, tedy A=A<<1+A=A*2+A=A*3, což je poměrně příjemné zjištění:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ *= 3;
;
        sta     tmp1
        asl     a
        clc
        adc     tmp1
;
; }
;
        rts

.endproc

V dalším příkladu zkusíme vynásobení pěti:

void main(void)
{
    __A__ = 42;
 
    __A__ *= 5;
}

V tomto případě je násobení nahrazeno posunem doleva o dva bity (tedy vynásobením čtyřmi) a součtem s původní hodnotou, tedy A=A<<2+A=A*4+A=A*5:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ *= 5;
;
        sta     tmp1
        asl     a
        asl     a
        clc
        adc     tmp1
;
; }
;
        rts
 
.endproc
Poznámka: v obou případech je výsledný kód poměrně dobrý – zde cc65 ukázal své lepší stránky.

17. Násobení větší konstantou

V případě, že konstanta, kterou je násoben obsah akumulátoru, je složitější, tj. pokud ji nelze snadno rozepsat na několik mocnin dvojky, bude namísto optimalizovaného kódu zavolán podprogram (subrutina) pro násobení. Připomeňme si totiž, že osmibitový mikroprocesor MOS 6502 neobsahoval instrukci pro součin, což je ostatně s ohledem na jeho cenu a interní jednoduchost pochopitelné:

void main(void)
{
    __A__ = 42;
 
    __A__ *= 42;
}

Ve výše uvedeném příkladu se pokoušíme násobit konstantou 42, což se přeloží do tohoto kódu:

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
        .fopt           compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main

; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------

.segment        "CODE"

.proc   _main: near

.segment        "CODE"

;
; __A__ = 42;
;
        lda     #$2A
;
; __A__ *= 42;
;
        jsr     pusha0
        jmp     tosumulax
 
.endproc
Poznámka: povšimněte si volání subrutiny pro násobení. Mimochodem – otrocký překlad by měl vypadat takto:
        jsr     pusha0
        jsr     tosumulax
        rts

Překladač dvojici jsr+rts zaměnil za jmp, protože ono rts je vlastně „schováno“ ve volané subrutině.

bitcoin_skoleni

18. Upravený soubor Makefile

Pro úplnost si ještě ukažme, jak vypadá soubor Makefile, kterým byly přeloženy všechny minule i dnes popsané demonstrační příklady. Tento soubor předpokládá, že je cc65 nainstalován tak, že je dostupný bez nutnosti uvedení celé cesty k překladači. V opačném případě je nutné změnit prvních několik řádků tohoto souboru:

CC65=cc65
 
STD_FLAGS =
INCLUDE_SOURCE =-T
STATIC_LOCAL_VARS = -Cl
STD_OPTIMIZATION = -O
REGISTER_VARS = -Or
INLINE_FUNC = -Oi
ALL_OPTIMIZATIONS = -Osir
 
all:    local_add_1.asm local_add_2.asm local_add_3.asm local_add_4.asm local_add_5.asm local_add_6.asm local_add_7.asm \
        memset1.asm memset2.asm memset3.asm memset4.asm memset5.asm memset6.asm \
        empty_main.asm local_var_1.asm local_var_2.asm register_var_1.asm register_var_2.asm \
        a_register.asm ax_register_1.asm ax_register_2.asm eax_register_1.asm eax_register_2.asm \
        post_increment_1.asm post_increment_2.asm pre_increment_1.asm pre_increment_2.asm \
        assembly1.asm assembly2.asm assembly3.asm assembly4.asm \
        a_register_add.asm a_register_shift.asm a_register_rshift.asm a_register_double.asm \
        a_register_triple.asm a_register_quadruple.asm \
        a_register_half.asm a_register_times5.asm a_register_times42.asm
 
clean:
        rm *.asm
 
local_add_1.asm:        local_add.c
        ${CC65} ${STD_FLAGS} -o $@ $<
 
local_add_2.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} -o $@ $<
 
local_add_3.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} -o $@ $<
 
local_add_4.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
local_add_5.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${REGISTER_VARS} -o $@ $<
 
local_add_6.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${INLINE_FUNC} -o $@ $<
 
local_add_7.asm:        local_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${ALL_OPTIMIZATIONS} -o $@ $<
 
local_add_B.asm:        local_add_B.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${INLINE_FUNC} -o $@ $<
 
local_mul.asm:  local_mul.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${ALL_OPTIMIZATIONS} -o $@ $<
 
memset1.asm:    memset1.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
memset2.asm:    memset2.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
memset3.asm:    memset3.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
memset4.asm:    memset4.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
memset5.asm:    memset5.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
memset6.asm:    memset6 .c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
empty_main.asm: empty_main.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
local_var_1.asm:        local_var.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
local_var_2.asm:        local_var.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${REGISTER_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
register_var_1.asm:     register_var.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
register_var_2.asm:     register_var.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${REGISTER_VARS} ${STD_OPTIMIZATION} -o $@ $<
 
a_register.asm: a_register.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
ax_register_1.asm:      ax_register.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} -o $@ $<
 
ax_register_2.asm:      ax_register.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
eax_register_1.asm:     eax_register.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} -o $@ $<
 
eax_register_2.asm:     eax_register.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
post_increment_1.asm:   post_increment.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} ${INLINE_FUNC} -o $@ $<
 
post_increment_2.asm:   post_increment.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${post_incrementS} ${STD_OPTIMIZATION} ${INLINE_FUNC} -o $@ $<
 
pre_increment_1.asm:    pre_increment.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STATIC_LOCAL_VARS} ${STD_OPTIMIZATION} ${INLINE_FUNC} -o $@ $<
 
pre_increment_2.asm:    pre_increment.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${pre_incrementS} ${STD_OPTIMIZATION} ${INLINE_FUNC} -o $@ $<
 
assembly1.asm:  assembly1.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${pre_incrementS} ${STD_OPTIMIZATION} ${REGISTER_VARS} -o $@ $<
 
assembly2.asm:  assembly2.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${pre_incrementS} ${STD_OPTIMIZATION} ${REGISTER_VARS} -o $@ $<
 
assembly3.asm:  assembly3.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${pre_incrementS} ${STD_OPTIMIZATION} ${REGISTER_VARS} -o $@ $<
 
assembly4.asm:  assembly4.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${pre_incrementS} ${STD_OPTIMIZATION} ${REGISTER_VARS} -o $@ $<
 
a_register_add.asm:     a_register_add.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_shift.asm:   a_register_shift.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_rshift.asm:  a_register_rshift.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_double.asm:  a_register_double.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_triple.asm:  a_register_triple.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_quadruple.asm:       a_register_quadruple.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_times5.asm:  a_register_times5.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_times42.asm: a_register_times42.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<
 
a_register_half.asm:    a_register_half.c
        ${CC65} ${STD_FLAGS} ${INCLUDE_SOURCE} ${STD_OPTIMIZATION} -o $@ $<

19. Repositář s demonstračními příklady

Demonstrační příklady napsané v jazyku C, které jsou určené pro překlad pomocí překladače cc65, byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 local_add.c funkce pro součet dvou celých čísel https://github.com/tisnik/8bit-fame/blob/master/cc65/local_add.c
2 memset1.c vyplnění bloku paměti zadanou hodnotou, základní verze https://github.com/tisnik/8bit-fame/blob/master/cc65/memset1.c
3 memset2.c vyplnění bloku paměti zadanou hodnotou, použití klíčového slova register https://github.com/tisnik/8bit-fame/blob/master/cc65/memset2.c
4 memset3.c vyplnění bloku paměti zadanou hodnotou, použití konstantních parametrů https://github.com/tisnik/8bit-fame/blob/master/cc65/memset3.c
5 memset4.c vyplnění bloku paměti zadanou hodnotou, přepis na smyčku while https://github.com/tisnik/8bit-fame/blob/master/cc65/memset4.c
6 memset5.c vyplnění bloku paměti zadanou hodnotou, přepis na smyčku do-while https://github.com/tisnik/8bit-fame/blob/master/cc65/memset5.c
7 memset6.c použití ukazatele namísto indexování přes pole https://github.com/tisnik/8bit-fame/blob/master/cc65/memset6.c
       
8 empty_main.c zdrojový kód obsahující pouze prázdnou funkci main https://github.com/tisnik/8bit-fame/blob/master/cc65/empty_main.c
9 local_var.c funkce main s lokální proměnnou https://github.com/tisnik/8bit-fame/blob/master/cc65/local_var.c
10 register_var.c funkce main s proměnnou s deklarací register https://github.com/tisnik/8bit-fame/blob/master/cc65/register_var.c
11 a_register.c manipulace s registrem A https://github.com/tisnik/8bit-fame/blob/master/cc65/a_register.c
12 ax_register.c manipulace s dvojicí registrů A a X https://github.com/tisnik/8bit-fame/blob/master/cc65/ax_register.c
13 eax_register.c manipulace s dvojicí registrů A, X a dvojicí paměťových míst https://github.com/tisnik/8bit-fame/blob/master/cc65/eax_register.c
14 post_increment.c použití operátoru ++ za operandem (postfixová varianta) https://github.com/tisnik/8bit-fame/blob/master/cc65/post_increment.c
15 pre_increment.c použití operátoru ++ před operandem (prefixová varianta) https://github.com/tisnik/8bit-fame/blob/master/cc65/pre_increment.c
16 assembly1.c mixování kódu v C a assembleru https://github.com/tisnik/8bit-fame/blob/master/cc65/assembly1.c
17 assembly2.c uložení výsledku do lokální proměnné jedinou instrukcí assembleru https://github.com/tisnik/8bit-fame/blob/master/cc65/assembly2.c
18 assembly3.c součet ručně optimalizovaný v assembleru https://github.com/tisnik/8bit-fame/blob/master/cc65/assembly3.c
19 assembly4.c taktéž součet, ovšem tentokrát s globálními proměnnými https://github.com/tisnik/8bit-fame/blob/master/cc65/assembly4.c
20 local_add_B.c součet bez použití assembleru https://github.com/tisnik/8bit-fame/blob/master/cc65/local_add_B.c
21 local_mul.c vynásobení dvou proměnných bez použití assembleru https://github.com/tisnik/8bit-fame/blob/master/cc65/local_mul.c
22 a_register_add.c přičtení jedničky k akumulátoru https://github.com/tisnik/8bit-fame/blob/master/cc65/a_register_add.c
23 a_register_shift.c aritmetický posun akumulátoru doleva https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_shift.c
24 a_register_rshift.c aritmetický posun akumulátoru doprava https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_rshift.c
25 a_register_half.c vydělení akumulátoru dvěma https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_half.c
26 a_register_double.c vynásobení akumulátoru dvěma https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_double.c
27 a_register_triple.c vynásobení akumulátoru třemi https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_triple.c
28 a_register_quadruple.c vynásobení akumulátoru čtyřmi https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_quadruple.c
29 a_register_times5.c vynásobení akumulátoru pěti https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_times5.c
30 a_register_times42.c vynásobení akumulátoru konstantou 42 https://github.com/tisnik/8bit-fame/blob/master/cc65/a_re­gister_times42.c
       
31 Makefile soubor zajišťující překlad všech demonstračních příkladů do assembleru s různými volbami https://github.com/tisnik/8bit-fame/blob/master/cc65/Makefile

20. Odkazy na Internetu

  1. When did people first start thinking ‚C is portable assembler‘?
    https://stackoverflow.com/qu­estions/3040276/when-did-people-first-start-thinking-c-is-portable-assembler
  2. The Thirty Million Line Problem
    https://www.youtube.com/wat­ch?v=kZRE7HIO3vk
  3. cc65 coding hints
    https://cc65.github.io/doc/co­ding.html
  4. NesDev.org
    https://www.nesdev.org/
  5. How to Program an NES game in C
    https://nesdoug.com/
  6. Cycle reference chart
    https://www.nesdev.org/wi­ki/Cycle_reference_chart
  7. Getting Started Programming in C: Coding a Retro Game with C Part 2
    https://retrogamecoders.com/getting-started-with-c-cc65/
  8. NES game development in 6502 assembly – Part 1
    https://kibrit.tech/en/blog/nes-game-development-part-1
  9. NES 6502 Programming Tutorial – Part 1: Getting Started
    https://dev.xenforo.relay­.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/
  10. List of 6502-based Computers and Consoles
    https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/
  11. 6502 – the first RISC µP
    http://ericclever.com/6500/
  12. ca65 Users Guide
    https://cc65.github.io/doc/ca65.html
  13. cc65 Users Guide
    https://cc65.github.io/doc/cc65.html
  14. ld65 Users Guide
    https://cc65.github.io/doc/ld65.html
  15. da65 Users Guide
    https://cc65.github.io/doc/da65.html
  16. “Hello, world” from scratch on a 6502 — Part 1
    https://www.youtube.com/wat­ch?v=LnzuMJLZRdU
  17. A Tour of 6502 Cross-Assemblers
    https://bumbershootsoft.wor­dpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/
  18. 6502 PRIMER: Building your own 6502 computer
    http://wilsonminesco.com/6502primer/
  19. 6502 Instruction Set
    https://www.masswerk.at/6502/6502_in­struction_set.html
  20. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  21. Single-board computer
    https://en.wikipedia.org/wiki/Single-board_computer
  22. www.6502.org
    http://www.6502.org/
  23. 6502 PRIMER: Building your own 6502 computer – clock generator
    http://wilsonminesco.com/6502pri­mer/ClkGen.html
  24. Great Microprocessors of the Past and Present (V 13.4.0)
    http://www.cpushack.com/CPU/cpu.html
  25. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  26. Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
    https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/
  27. Mikrořadiče a jejich použití v jednoduchých mikropočítačích
    https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/
  28. Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
    https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/
  29. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  30. Comparison of instruction set architectures
    https://en.wikipedia.org/wi­ki/Comparison_of_instructi­on_set_architectures
  31. Vývojové nástroje používané v dobách osmibitových mikropočítačů
    https://www.root.cz/clanky/vyvojove-nastroje-pouzivane-v-dobach-osmibitovych-mikropocitacu/
  32. Historie vývoje počítačových her (112. část – vývojové nástroje pro herní konzole)
    https://www.root.cz/clanky/historie-vyvoje-pocitacovych-her-112-cast-vyvojove-nastroje-pro-herni-konzole/
  33. Programovací jazyky a vývojové nástroje pro mikropočítače společnosti Sinclair Research
    https://www.root.cz/clanky/pro­gramovaci-jazyky-a-vyvojove-nastroje-pro-mikropocitace-spolecnosti-sinclair-research/
  34. Programovací jazyky používané na platformě osmibitových domácích mikropočítačů Atari
    https://www.root.cz/clanky/pro­gramovaci-jazyky-pouzivane-na-platforme-osmibitovych-domacich-mikropocitacu-atari/
  35. Programovací jazyky používané na platformě osmibitových domácích mikropočítačů Atari (2)
    https://www.root.cz/clanky/pro­gramovaci-jazyky-pouzivane-na-platforme-osmibitovych-domacich-mikropocitacu-atari-2/
  36. Cross assemblery a cross překladače pro platformu osmibitových domácích mikropočítačů Atari
    https://www.root.cz/clanky/cross-assemblery-a-cross-prekladace-pro-platformu-osmibitovych-domacich-mikropocitacu-atari/
  37. C Isn't A Programming Language Anymore
    https://faultlore.com/blah/c-isnt-a-language/
  38. Why the C Language Will Never Stop You from Making Mistakes
    https://thephd.dev/your-c-compiler-and-standard-library-will-not-help-you
  39. Benchmark: C compilers for the 6502 CPU
    https://sgadrat.itch.io/super-tilt-bro/devlog/219534/benchmark-c-compilers-for-the-6502-cpu
  40. Advanced optimizations in CC65
    https://github.com/ilmenit/CC65-Advanced-Optimizations
  41. The 6502/65C02/65C816 Instruction Set Decoded
    https://llx.com/Neil/a2/opcodes.html
  42. 6502 C Compilers Comparison
    https://gglabs.us/node/2293
  43. 6502 C compilers benchmark
    https://github.com/sgadrat/6502-compilers-bench
  44. cc65: Differences to the ISO standard
    https://cc65.github.io/doc/cc65­.html#s4
  45. Compilers
    http://www.6502.org/tools/lang/
  46. Překladače jazyka C pro historické osmibitové mikroprocesory
    https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.