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:
Instrukce | Popis | Operace |
---|---|---|
LD Rd,X | Load Indirect | Rd ← (X) |
LD Rd,X+ | Load Indirect and Post-Increment | Rd ← (X); X ← X+1 |
LD Rd,Y | Load Indirect | Rd ← (Y) |
LD Rd,Y+ | Load Indirect and Post-Increment | Rd ← (Y); Y ← Y+1 |
LD Rd,Z | Load Indirect | Rd ← (Z) |
LD Rd,Z+ | Load Indirect and Post-Increment | Rd ← (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:
Instrukce | Popis | Operace | Takty |
---|---|---|---|
IJMP | Indirect Jump to (Z) | PC(15:0) ← Z; PC(21:16) ← 0 | 2 |
EIJMP | Indirect Jump to (Z) | PC(15:0) ← Z; PC(21:16) ← EIND | 2 |
ICALL | Indirect Call to (Z) | PC(15:0) ← Z; PC(21:16) ← 0; … | 3/4 |
EICALL | Indirect 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
OpTab
zabírá každá instrukce jedno slovo. Pokud použiji instrukci avr.isa.rjmp;, je tento předpoklad splněn. Je třeba si uvědomit, že instrukce skoku RJMP může předat řízení/skočit v rozsahu -2048 / +2047 slov od aktuální adresy. Procedury realizující jednotlivé instrukce virtuálního procesoru se tedy musí nacházet v této vzdálenosti od své položky v OpTab
.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í.