Page 1 sur 1

[TUTO]-Base d'un trés simple Emulateur

Publié : lun. 27/déc./2010 16:49
par flaith
Bonjour,

vous savez surement ce qu'est un émulateur, c'est un programme qui va convertir un code en provenance d'un micro d'ancienne génération, par exemple, et qui va lui redonner une nouvelle jeunesse :mrgreen:

Ici il s'agit juste d'émuler du code et de le convertir pour le faire fonctionner sur nos machines actuelles.

Ceci n'est qu'une ébauche et il existe plusieurs façons de programmer un émulateur.

Ici je vais faire intervenir la fonction CallFunctionFast pour appeler des procédures.

Ici nous partons sur 5 opcodes, $01 (pour faire une addition), $02 (soustraction), $03 (multiplication), $04 (division) et $EA qui ne fait rien du tout.
Pour rappel, les opcodes ne sont que des octets mais qui, pour le processeur veulent dire quelque chose, et l'émulateur va les traduire...

Voici donc le code, commenté, on va juste prendre chaque octet dans la mémoire, incrémenter la position dans la mémoire, vérifier s'il correspond à un opcode connu et lancer la procédure qui lui est associée:

Code : Tout sélectionner

;VerySimpleEmulator
;Flaith-27.12.2010

;-Datasection
DataSection
  _DS_Memory:
    Data.u  34                      ;Nombre de données
    Data.a  $00,$21,$30             ;$00 indique une addition de $21 et de $30
    Data.a  $00,$F0,$50             ;Additionne $F0 à $50
    Data.a  $01,$33,$45             ;$01 est une soustraction
    Data.a  $01,$F0,$53
    Data.a  $02,$33,$03             ;$02 est une multiplication
    Data.a  $03,$DE,$45             ;$03 est une division
    Data.a  $EA                     ;$EA = NOP ==> ne fait rien du tout
    Data.a  $EA
    Data.a  $EA
    Data.a  $00,$01,$01
    Data.a  $01,$01,$01
    Data.a  $02,$01,$01
    Data.a  $03,$01,$01
    Data.a  $FF                     ;Fin du programme
EndDataSection

;-Globals
Global Dim Instruction.i(255)       ;Un entier car on conserve l'adresse de chaque procédure
Global Dim Memory.a($FFFF)          ;On créé une mémoire de 65535 Octets
Global PC.i                         ;Program Counter : A quel endroit est notre opcode ? (index)

;-Procédures Internes
; Affiche en hexa
Procedure.s Hexa(__Value.a)
  ProcedureReturn RSet(Hex(__Value,#PB_Ascii),2,"0")
EndProcedure

; Récupère un octet dans la mémoire incrémentée automatiquement
Procedure.a GetMemory()
  PC + 1
  ProcedureReturn Memory(PC)
EndProcedure

;-Procédures liées aux Opcodes
Procedure.a add()
  Protected.a a,b

  a = GetMemory()
  b = GetMemory()
  Debug "----Add $"+hexa(a)+" to $"+hexa(b)
  ProcedureReturn a+b
EndProcedure

Procedure.a sub()
  Protected.a a,b

  a = GetMemory()
  b = GetMemory()
  Debug "----Sub $"+hexa(a)+" to $"+hexa(b)
  ProcedureReturn a-b
EndProcedure

Procedure.a mul()
  Protected.a a,b

  a = GetMemory()
  b = GetMemory()
  Debug "----Mul $"+hexa(a)+" to $"+hexa(b)
  ProcedureReturn a*b
EndProcedure

Procedure.a div()
  Protected.a a,b

  a = GetMemory()
  b = GetMemory()
  Debug "----Div $"+hexa(a)+" to $"+hexa(b)
  ProcedureReturn a/b
EndProcedure

Procedure.a nop()
  Debug "----Nop"
  ProcedureReturn $EA
EndProcedure

;Liaison Tableau des instructions avec l'adresse de chaque procédure
Instruction($00) = @add()
Instruction($01) = @sub()
Instruction($02) = @mul()
Instruction($03) = @div()
Instruction($EA) = @nop()

;-Lecture des opcodes et insertion dans la mémoire
Restore _DS_Memory
Read.u longueur
For i = 0 To longueur-1
  Read.a aValue
  Memory(i) = aValue
Next i

; On commence à zéro
PC = 0
Opcode = Memory(PC)

;-Boucle principale
While Opcode <> $FF
  ;On appelle les procédures liées aux opcodes
  Debug "$"+Hexa(CallFunctionFast(Instruction(Opcode)))
  Opcode = GetMemory()
Wend
Debug "----End"
End
On peut rajouter autant de procédures que d'instructions (mais limité quand même à 255) - Si une procédure n'appelle qu'un seul argument, il suffit de faire un appel à la fonction GetMemory() qu'une seule fois, ou s'il n'y a pas d'argument, ne rien faire du tout (Code $EA) :mrgreen:

Re: [TUTO]-Base d'un trés simple Emulateur

Publié : mar. 18/janv./2011 20:49
par flaith
Suite de ce petit tutoriel sur la construction d'un émulateur/désassembleur

Nous allons prendre l'exemple du processeur 6502 qui était , pour les plus vieux d'entre nous :wink: , embarqué dans les machines 8bits de l'époque comme l'Apple II, le Commodore 64 ou l'Atari 2600 par exemple (et bien d'autres)

Ce processeur a plusieurs mode d'adressage, c'est a dire que pour un type d'instruction comme le "ADC" (permettant de faire l'addition de deux valeurs) on peut définir plusieurs façon de lire ces deux valeurs, par exemple, soient elles sont immédiates (utilisation d'un seul octet) ou absolu (utilisation de deux octets).

Chaque instruction a au moins un mode d'adressage et certaines peuvent en avoir plusieurs mais le nombre maximum est de 13 modes pour le 6502.

Mais émuler (ou faire un désassembleur) revient à lire chaque octet les uns à la suite des autres, on prend le premier, on le recherche dans notre liste et on traite les octets qui suivent (aucun, 1 ou 2 octets) en fonction du mode d'adressage de cet octet.

Donc il faut indiquer à l'émulateur, pour chaque octet du 6502, quel est le nom de la procédure qu'il faut appeler, le mode d'adressage et ensuite tout afficher.

Prenons le cas de l'addition (opcode 'ADC'), il y a plusieurs modes d'adressage pour ce code, on va définir tout ca grace à des tableaux et, pour que cela soit plus simple, plus rapide et plus lisible, on va créer les tableaux, les procédures et surtout, les macros.

Les tableaux

Code : Tout sélectionner

Global Dim Tbl_Opcode.s(255), Dim Tbl_AdrProcedure.i(255), Dim Tbl_ModeAdressage.i(255)
Donc on a 255 opcodes maximum, idem pour les procédures et modes d'adressage (bien sur tout ceci peut-être optimisé, mais là n'est pas le but, enfin dans l'immédiat)

On utilise une valeur global pour l'octet en cours qui est lu dans le fichier par exemple

Code : Tout sélectionner

Global.i CurrentOpcode
Et maintenant les macros (quelle belle création ces macros :D )

Code : Tout sélectionner

Macro quote
  "
EndMacro

Macro InitializeCPU(__opcode__, __procedure__, __mode__)
  Tbl_Opcode(__opcode__)        = quote#__procedure__#quote   ;On utilise le nom de la procedure en plus d'avoir le Nom de l'opcode
  Tbl_AdrProcedure(__opcode__)  = @__procedure__()            ;Ici on met l'adresse de la procedure que l'on appellera
  Tbl_ModeAdressage(__opcode__) = @ADRMODE_#__mode__()        ;Le mode d'adressage permet d'avoir le nombre d'octets utilisé par l'opcode
EndMacro
Donc deux macros, une pour gérer les guillemets et l'autre pour enrichir nos tableaux

Puis viennent les procédures d'adressage (elles ne sont pas toutes présentes)

Code : Tout sélectionner

;********* Différents mode d'adressage
;* #Implied *
Procedure ADRMODE_implied()
  ProcedureReturn 0
EndProcedure
;* #Immediate *
Procedure ADRMODE_immediate()
  ProcedureReturn 1
EndProcedure
;* ABS *
Procedure ADRMODE_abs()
  ProcedureReturn 2
EndProcedure
;* Branch *
Procedure ADRMODE_relative()
  ProcedureReturn 1
EndProcedure
...
Les instructions

Code : Tout sélectionner

;********* Instructions
Procedure ADC()
  Protected.i _iNBValue = CallFunctionFast(Tbl_ModeAdressage(CurrentOpcode))

  Debug "On utilise la procédure '"+Tbl_Opcode(CurrentOpcode)+"' avec "+Str(_iNBValue)+" valeur(s)"
EndProcedure
Interprétations des octets

Code : Tout sélectionner

;********* Recupère les infos par Opcode
Procedure GetInfoOpcode(__Opcode.i)
  CurrentOpcode = __Opcode
  CallFunctionFast(Tbl_AdrProcedure(CurrentOpcode))
EndProcedure
On initialise nos valeurs grâce aux macros

Code : Tout sélectionner

;********* Initialise les opcodes, leur valeurs et modes d'adressage
InitializeCPU($69, ADC, immediate)
InitializeCPU($6D, ADC, abs)
Un petit test pour vérifier

Code : Tout sélectionner

;********* Test
GetInfoOpcode($69)
GetInfoOpcode($6D)
Le listing complet

Code : Tout sélectionner

Global Dim Tbl_Opcode.s(255), Dim Tbl_AdrProcedure.i(255), Dim Tbl_ModeAdressage.i(255)
Global.i CurrentOpcode

Macro quote
  "
EndMacro

Macro InitializeCPU(__opcode__, __procedure__, __mode__)
  Tbl_Opcode(__opcode__)        = quote#__procedure__#quote   ;On utilise le nom de la procedure en plus d'avoir le Nom de l'opcode
  Tbl_AdrProcedure(__opcode__)  = @__procedure__()            ;Ici on met l'adresse de la procedure que l'on appellera
  Tbl_ModeAdressage(__opcode__) = @ADRMODE_#__mode__()        ;Le mode d'adressage permet d'avoir le nombre d'octets utilisé par l'opcode
EndMacro

;********* Différents mode d'adressage
;* #Implied *
Procedure ADRMODE_implied()
  ProcedureReturn 0
EndProcedure
;* #Immediate *
Procedure ADRMODE_immediate()
  ProcedureReturn 1
EndProcedure
;* ABS *
Procedure ADRMODE_abs()
  ProcedureReturn 2
EndProcedure
;* Branch *
Procedure ADRMODE_relative()
  ProcedureReturn 1
EndProcedure

;********* Instructions
Procedure ADC()
  Protected.i _iNBValue = CallFunctionFast(Tbl_ModeAdressage(CurrentOpcode))

  Debug "On utilise la procédure '"+Tbl_Opcode(CurrentOpcode)+"' avec "+Str(_iNBValue)+" valeur(s)"
EndProcedure

;********* Recupère les infos par Opcode
Procedure GetInfoOpcode(__Opcode.i)
  CurrentOpcode = __Opcode
  CallFunctionFast(Tbl_AdrProcedure(CurrentOpcode))
EndProcedure

;********* Initialise les opcodes, leur valeurs et modes d'adressage
InitializeCPU($69, ADC, immediate)
InitializeCPU($6D, ADC, abs)

;********* Test
GetInfoOpcode($69)
GetInfoOpcode($6D)
Bon amusement si j'ose dire :mrgreen:

Re: [TUTO]-Base d'un trés simple Emulateur

Publié : mar. 18/janv./2011 21:30
par Warkering
Merci beaucoup de partager ces codes, c'est très instructif! :wink: