40.7.3.1. Instruction Fetch and decode

Načítání a dekódování instrukce

FIXME:Udělat instrukce aktivními odkazy.

Jednou z operací kterou je třeba realizovat, je nahrání instrukce simulovaného procesoru z paměti a dekódování této instrukce. Následující příklad vychází z předpokladu, že paměť simulovaného procesoru je v RAM našeho AVR, nebo že je použita externí RAM mechanizmy AVR například u čipu ATmega162.

Operace „Fetch“, tedy přečtení instrukce z operační paměti je v podstatě jednoduché přečtení buňky v paměti RAM na adrese dané naším čítačem instrukcí PC a zapsání této hodnoty do registru Instr kde se bude provádět dekódování instrukce. Nezbytnou součástí je také posunutí PC na další instrukci.

mem[PC] → Instr
PC = PC+1

Z instrukcí, které umožňují přístup do paměti RAM, se nám hodí jen:

InstrukcePopisOperace
LD Rd,XLoad IndirectRd ← (X)
LD Rd,X+Load Indirect and Post-IncrementRd ← (X); X ← X+1
LD Rd,YLoad IndirectRd ← (Y)
LD Rd,Y+Load Indirect and Post-IncrementRd ← (Y); Y ← Y+1
LD Rd,ZLoad IndirectRd ← (Z)
LD Rd,Z+Load Indirect and Post-IncrementRd ← (Z); Z ← Z+1

Musíme se tedy rozhodnout, v kterém registru budeme uchovávat hodnotu čítače instrukcí PC. Registr (registrový pár) Z používají rovněž instrukce LPM, ELPM a SPM pro práci s pamětí programu. Z toho důvodu bych je nepoužil pro registr PC. Zbývají registry X a Y jenž jsou rovnocenné. Mohu si tedy vybrat kterýkoliv z nich. V dalším pak používám jako PC registrový pár X.

Kód pro Fetch tedy vypadá takto

Fetch:
        ld Instr, X+            ; Přečtení instrukce z RAM

Takhle jednoduché je to proto, že používáme instrukce pro práci s vnitřní či vnější pamětí RAM, a taky proto, že jsme mohli vyhradit registr X pro čítač instrukcí. Pokud bychom si nemohly vyhradit registr X, a museli používat jeden z X,Y,Z jen dočasně, mohli bychom si vypomoci instrukcí MOVW.

Fetch:
        movw    Z, PC           ; načtění hodnoty PC do indexového registru
        ld      Instr, Z+       ; přečtení instrukce z RAM
	movw    PC, Z           ; uložení nové hodnot zěpt do PC

Výsledný program tedy bude o tyto dvě MOVW instrukce pomalejší. Protože každá si veme jeden hodinový takt, kód je o dva takty delší. Samotná instrukce LD spotřebuje další dva takty pro čtení z vnitřní RAM. V případě externí paměti RAM může dojít a patrně dojde ještě k dalšímu spomalení. Pro konkrétní hodnoty a použití externí RAM najdete informace o potřebném počtu taktů hodinového signálu v dokumentaci daného procesoru.

Dalším krokem je dekódování instrukčního kódu načteného do registru Instr. Tady hodně záleží na instrukční sadě našeho virtuálního procesoru. Některé mikroprocesory, jako je například CDP 1802, mají takovou instrukční sadu, že se kód instrukce rozpadá na dvě pole. První určuje instrukci a druhé registr či variantu instrukce. Takovou instrukční sadu bych pravděpodobně dekódoval programově.

Při bližším pohledu do instrukční sady AVR, nalezneme instrukce:

InstrukcePopisOperaceTakty
IJMPIndirect Jump to (Z)PC(15:0) ← Z; PC(21:16) ← 02
EIJMPIndirect Jump to (Z)PC(15:0) ← Z; PC(21:16) ← EIND2
ICALLIndirect Call to (Z)PC(15:0) ← Z; PC(21:16) ← 0; …3/4
EICALLIndirect Call to (Z)PC(15:0) ← Z; PC(21:16) ← EIND; …4

Jsou to jediné instrukce, které dovolují nepřímý skok nebo volání podprogramu. S použitím těchto instrukcí mohu napsat něco jako:

Decode: ; Dekódování instrukce.
        ldi ZL, low(OpTab)
        ldi ZH, high(OpTab)
        add ZL, Instr           ; získání adresy v tabulce OpTab
        ijmp                    ; skok na odpovídající řádek v tabulce

Místo instrukce ICALL/EICALL jsem použil IJMP, případně EIJMP. Důvodem je, že neplánuji kód jednotlivých virtuálních instrukcí volat ještě z jiného místa v programu, a chci ušetřit pár taktů. Proto místo, pro někoho možná logického

        …
        icall
        …

Kdy jednotlivé podprogramy končí instrukcí RET. Použiji

        …
        ijmp
NextInstruction:
        …

A jednotlivé "podprogramy" ukončím instrukcí RJMP NextInstruction. Tato instrukce ja je rovněž rychlejší než instrukce RET.

Pro maximální zrychlení emulace, provádím řadu optimalizací v nejčastěji používaném kódu, t.j. ve zde uvedeném kódu nahrávání a dekódování instrukce. Dekódování provádím skokem na proceduru podle obsahu instrukce (OpCode). Tabulka skoků má 256 položek. Pro emulování jednoduchého procesoru stačí jedna takováto tabulka. Pro emulování procesoru s velmi bohatou sadou instrukcí, jako je například Z80 bych takovýchto tabulek potřeboval více. Důvodem jsou prefixové instrukce tohoto procesoru.

Ve svém prvním pokusu se omezuji na jednoduchý procesor Forthovského typu.

; Pracovní registr, mé oblíbené jméno je W.  Tento registr musí být mezi
; horními 16 (tedy 16-31) protože je často plněn instrukcí ldi.
.def    W       = R16

; Registry použité při emulaci virtuálního procesoru.
.def    Zero    = R12   ; Konstanta 0 použítá k urychlení některých výpočtů.
.def    OpCode  = R13   ; Registr obsahující načtenou instrukci k dekódování.
.def    OpTabL  = R14   ; Dvojce registrů tvořící ukazatel na tabulku uperací.
.def    OpTabH  = R15   ;+

; Před spuštěním emulace je třeba naplnit některé registry počátečními
; hodnotami.  Jedná se zejména o registry Zero a OpTab jenž slouží jako
; konstanty pro urychelní operací.
       ; Naplnění OpTabL:OpTabH adresou OpTab
       ldi     W, low(OpTab)
       mov     OpTabL, W
       ldi     W, high(OpTab)
       mov     OpTabH, W

       ; Naplnění Zero konstantou 0
       ldi     W, 0
       mov     Zero, W

; Čítač instrukcí virtuálního procesoru je v registrovém páru X.

Fetch:  ld      OpCode, X++     ; Získání instrukce z paměti
        ; Dekódování instrukce pomocí OpTab
        movw    ZL, OpTabL      ; Move OpTab address into Z
        add     ZL, OpCode      ; Z = Z+OpCode
        adc     ZH, Zero        ;+
        ; Nyní ukazuje Z na n-tou položku OpTab podle hodnoty OpCode
        ijmp                    ; Předáme řízení na příslušnou proceduru.


; Tabulka OpTab.  Pro každou hodnotu OpCode je zde adresa (skok) na rutinu
; realizující příslušnou instrukci virtuálního procesoru.  Tabulka obsahuje
; maximálně 256 položek.  Pokud obsahuje méně než 256 položek, měli bychom
; zajistit že nebude dekódována instrukce která není v tabulce.
OpTab:
        rjmp    INOP            ; OpCode=0, NOP
        rjmp    IDUP            ; OpCode=1, DUP
        rjmp    IDROP           ; OpCode=2, DROP
        rjmp    IADD            ; OpCode=3, ADD
        ...
        rjmp    ILAST           ; OpCode=255, LAST

; Instrukce NOP neprovádí nic, pouze se vrátí zpět do do smyčky emulátoru.
INOP:   rjmp    Fetch

IDUP:   ...
        ...
        rjmp    Fetch

Poznámky ke kódu

Varování

Uvedený kód nebyl testován. Podařilo se mi ho bez problémů přeložit assemblerem, ale není zaručena jeho funkčnost. Je třeba ještě zkontrolovat několik věcí.

Licence Creative Commons
Elektronika a počítače, jejímž autorem je Radek Hnilica, podléhá licenci Creative Commons Uveďte autora-Nevyužívejte dílo komerčně-Zachovejte licenci 3.0 Česká republika .