Como Desenvolver um Emulador de Computador
por Marat Fayzullin

Traduo de Rodrigo Gonzatto (Wolf), membro da BR Games
Site: http://www.kinox.org/brgames

Eu escrevi este  documento depois de  receber um grande quantia de
emails  de  pessoas  que  gostariam  de  escrever  um  emulador de
um computador ou outro, mas no sabiam por onde para comear.
Todas  as  opnies e conselhos contidos neste arquivo so de minha
autoria e no devem ser tomados como verdade absoluta. O documento
cobre  principalmente  a  "interpretao" dos  emuladores, e no a
"compilao"  destes,  pois eu no possuo muita experincia com as
tcnicas de recompilao. Em consequncia disto, aponto alguns dos
lugares onde voc pode encontrar informao sobre estas tcnicas. 

Se voc sente que falta algo neste documento,  ou deseja fazer uma
correo, sinta-se livre para mandar um e-mail pra mim com os seus
comentrios.  Eu no darei resposta  idiotas, chatos e pedidos de
ROMs. Eu acabei esquecendo alguns endereos FTP/WWW,  que deveriam
estar no fim deste documento,  ento,  se voc sabe de qualquer um
que deva ter o mrito de estar l, faa contato comigo.  Faa isso
tambm se estiver com alguma dvida que no foi respondida aqui. 


Contedo

Ento voc decidiu  escrever um  software de emulador?  Muito bem,
pois  este documento responde  s perguntas tcnicas comuns  sobre
como escrever emuladores, podendo assim lhe ajudar em algo.

     O que pode ser emulado?
     O que  "emulao" e o que a difere de "simulao"?
      legal emular o hardware de um proprietrio?
     O que  "interpretar o emulador" e o que o difere de
       "recompilar o emulador"?
     Eu desejo escrever um emulador. Por onde devo comear?
     Que linguagem de programao eu devo usar?
     Onde eu obtenho informaes do hardware a ser emulado?
     Como eu emulo uma CPU?
     Como eu optimizo meu cdigo C?
     O resto est por vir...


# O que pode ser emulado?

Basicamente qualquer  coisa  que tenha um microprocessador dentro.
Certamente,  os dispositivos mais interessantes para se emular so
os que funcionam com mais ou menos flexibilidade. Incuindo:

     Computadores
     Calculadoras
     Consoles de Videogames
     Arcades de Videogames
     etc.

 sempre  bom  lembrar  que voc  pode emular  qualquer sistema de
computador, mesmo os mais complexos ( como o computador  Commodore
Amiga, por exemplo). Porm, nestes casos a performance da emulao
pode ser muito baixa.


# O que  "emulao" e o que a difere de "simulao"?

Emulao    a  tentativa  de  imitar  o  desenho  interior de  um
dispositivo. Simulao  a tentativa  de imitar  as  funes de um
dispositivo. Exemplos: um programa que imita o hardware  do arcade
de Pacman para rodar a verdadeira  ROM  de Pacman  um emulador. O
jogo  do Pacman escrito para um computador usar grficos parecidos
com o real arcade  um simulador.


#  legal emular o hardware de um proprietrio?

Embora muitos digam o contrrio, ao que me parece  legal emular o
hardware  de  um  proprietrio, se  as  informaes dele no forem
obtidas por meios ilegais. Voc deve estar atento ao fato de que 
ilegal  distribuir ROMs de sistema ( BIOS, etc. ) com emulador, se
este  estiver em  estado  "copyrighted" (cpia registrada).


# O que  "interpretar o emulador" e o que o difere de "recompilar
  o emulador"? 

H trs esquemas bsicos que podem ser utilizados por um emulador.
Eles podem ser combinados para se obter um melhor resultado. 

     Interpretao

     O emulador  l o cdigo emulado  da memria  byte-por-byte, o
decodifica  e  desempenha  os comandos  apropriados  nos registros
emulados, memria, e I/O. O algoritimo geral de tal emulador    o
seguinte:


     while(CPUIsRunning)
     {
       Fetch OpCode
       Interpret OpCode
     }

     O melhor deste cdigo  a facilidade do debug,  portabilidade
e  sincronizao  ( voc pode simplesmente contar os ciclos  que o
relgio faz  e  dar descanso    emulao por conta do ciclo ).  O
simples, grande e bvio problema  a performance.  A interpretao
toma muito de tempo da CPU, e voc pode  precisar de um computador
rpido para rodar seu cdigo numa velocidade decente. 

     Recompilao Esttica

     Nesta tcnica, voc pega o programa escrito no cdigo emulado
e tenta traduz-lo para o cdigo de montagem  (cdigo assembly) de
seu computador.  O resultado  ser um usual arquivo executvel que
voc  poder rodar no seu computador  sem nenhuma outra ferramenta
especial.
     Apesar de recompilao  esttica soar bem melhor,  nem sempre
ela  ser   possvel.   Por exemplo,  voc   no  pode  recompilar
estaticamente um cdigo auto-modificado (self-modifying), pois no
existe jeito  de  faze-l rodar.  Para evitar tais situaes, voc
pode  tentar combinar a recompilao  esttica com a interpretao
ou recompilao dinmica.

     Recompilao Dinmica

     A recompilao  dinmica    essencialmente a mesma coisa que
a esttica, a diferena  que ocorre durante execuo do programa.
Ao  invs de tentar  recompilar todo o cdigo em uma s vez,  voc
vai aonde quer com as instrues CALL ou JUMP.
Para aumentar a velocidade,  esta tcnica deve ser combinada com a
recompilao  esttica.  Voc pode ler mais  sobre a  recompilao
dinmica  no  white paper (papel branco),  por Ardi,  criadores da
recompilao do emulador de Macintosh. 


# Eu desejo escrever um emulador. Por onde devo comear? 

Para escrever um emulador, voc deve ter um bom conhecimento geral
de  programao de computadores  e  de eletrnica digital.  Alguma
experincia em programao em assembly lhe dar uma boa mo.

 1.Escolha uma linguagem de programao para usar.
 2.Ache toda informao disponvel sobre o hardware a ser emulado.
 3.Escreva a emulao  da CPU ou obtenha o cdigo real da emulao
   da CPU.
 4.Faa algum esboo do cdigo para emular o descanso do hardware,
   ao menos parcialmente.
 5.Agora,  ser  muito til escrever um pequeno debugger embutido,
   que  permita  parar  a  emulao e ver  o  que  o programa est
   fazendo.  Talvez voc precise de um disassembler da emulao da
   linguagem  do  sistema assembly.  Escreva  o seu prprio se no
   exitir um destes.
 6.Tente rodar programas no seu emulador.
 7.Use  o disassembler e  o debugger para ver que programas usam o
   hardware e ajuste seu cdigo apropriadamente.



# Que linguagem de programao eu devo usar?

As alternativas mais bvias so C e Assembly. Aqui esto os prs e
contras de cada uma delas: 

     Assembly

     + Geralmente permite produzir um cdigo rpido.
     + Os  registros  emulados  da  CPU podem  ser utilizados para
       armazenar diretamente os registros da CPU emulada.
     + Muitos opcodes podem  ser emulados com os opcodes similares
        CPU emulada.
     - O cdigo no  porttil, ou seja, no ser possvel rod-lo
       num computador com arquitetura diferente.
     -  difcil o debug e a manuteno do cdigo.

     C

     + O cdigo pode se tornar  porttil se trabalhar em  sistemas
       operacionais e computadores diferentes.
     +  relativelmente fcil o debug e a manuteno do cdigo.
     + As  diferentes  hipteses de  como trabalha o hardware real
       podem ser testadas rapidamente.
     - C , geralmente, mais lento que o cdigo assembly puro.

Bom conhecimento da linguagem escolhida  absolutamente necessrio
para se escrever um emulador que funcione, assim como, num projeto
complexo  como este,  o cdigo deveria  ser  optimizado para poder
rodar o mais rpido possvel.
Emulao de computadores definitivamente no   um dos projetos em
que voc possa aprender uma linguagem de programao.


# Onde eu obtenho informaes do hardware a ser emulado? 

A seguir h uma lista de lugares onde voc pode achar o que quer.

NEWSGROUPS (Grupos de Notcias)

  comp.emulators.misc
     Este  um grupo de notcias de discusso geral sobre emulao
de computadores.  Muitos autores do emuladores lem ele,  embora o
nvel de barulho seja muito alto.

  comp.emulators.game-consoles
     O mesmo  que  o comp.emulators.misc, s que mais especificado
na emulao de consoles de videogames.

  comp.sys./emulated-system/
     A hierarquia comp.sys.* contm grupos de notcias dedicados 
computadores especficos. Voc pode obter muita informao tcnica
til na leitura desses grupos. Exemplos tpicos:

  comp.sys.msx       Computadores MSX/MSX2/MSX2+/TurboR
  comp.sys.sinclair  Sinclair ZX80/ZX81/ZXSpectrum/QL
  comp.sys.apple2    Apple ][
  etc.

     Por favor, cheque sempre as FAQS apropriadas 
     antes de postar em qualquer grupo.
  alt.folklore.computers
  rec.games.video.classic

FTP

Oulu
ftp://x2ftp.oulu.fi/pub


WWW

Emulation Programmers Resource
http://www.classicgaming.com/EPR/

Emulation (emulao de macintosh)
http://www.emulation.net/

Programmer Heaven
http://www.programmershaven.com/

Komkon
http://www.komkon.org/


FAQ

D uma olhada nos sites (WWW) e nos grupos de notcias


# Como eu emulo uma CPU?

Antes de tudo, se voc apenas necessitar emular o padro Z80  ou a
CPU 6502,  voc  dever  usar um dos emuladores  de  CPU que eu j
citei. Devem ser aplicadas certas condies para seu uso correto.
Para que deseja escrever  seu prprio cdigo de emulao de CPU ou
est interessado  em saber como ele trabalha,  eu forneo o cdigo
bsico  de um emulador de CPU tpico em linguagem C,  logo abaixo.
Para o seu  prprio  emulador,  voc  pode  querer retirar algumas
partes ou adicionar outras para personaliz-lo.


Counter=InterruptPeriod;
PC=InitialPC;

for(;;)
{
  OpCode=Memory[PC++];
  Counter-=Cycles[OpCode];

  switch(OpCode)
  {
    case OpCode1:
    case OpCode2:
    ...
  }

  if(Counter<=0) {
    /* checa por interrupes e faz outra emulao do hardware */
      ...
    counter+="InterruptPeriod;"
    if(exitrequired) break;
  }
}


Primeiro, ns designamos os  valores iniciais ao contador de ciclo
da CPU (Counter) e ao contador do programa (PC):

Counter=InterruptPeriod;
PC=InitialPC;

O contador  contm  o nmero de ciclos que a CPU deixa  prxima -
suspeitada - interrupo.  Note que a interrupo no deve ocorrer
necessariamente  quando  o contador  expirar:  voc pode usar isto
para  muitos  outros propsitos,  tal como sincronizar timers,  ou
atualizar os scanlines da tela. Mais deixe isto pra mais tarde.  O
PC contm uma memria endereada,  que a nossa CPU emulada ler no
seu prximo opcode.

Depois  que  os  valores iniciais so designados,  ns iniciamos o 
loop principal:

for(;;)
{

Note que este loop tambm pode ser implementado com:

while(CPUIsRunning)
{

Aqui, CPUIsRunning  uma varivel boolean. Ela tem suas vantagens,
como a  de  voc poder terminar o loop a qualquer momento, setando
CPUIsRunning=0.  Infelizmente, checando esta varivel toda vez que
ela  passar, tomar  muito  de  tempo da CPU. Seria bom que voc a
evita-se quando possvel, porm, no implemente este loop com:

while(1)
{

Porque, neste caso, alguns compiladores geraro um cdigo checando
se 1  verdadeiro ou no. Voc certamente no deseja um compilador
fazendo um trabalho desnecessrio em toda passagem de um loop.
Agora ns estamos no loop, e  a  primeira coisa ser ler o prximo
opcode, que modifica o contador de programa: 

OpCode=Memory[PC++];

Este  o caminho mais simples e rpido para ler a memria emulada,
porm, ela no ser possvel pelas seguintes razes:

  Memria  pode ser  fragmentada  dentro  das  pginas  de switch
  (bancos aka) 
  L podem ser mapeadas a memria dos dispositivos I/O do sistema

Nestes  casos,  ns  podemos  ler  a  memria  emulada pela funo
ReadMemory(): 

OpCode=ReadMemory(PC++);

Geralmente  a  funo WriteMemory() serve para escrever na memria
emulada. Alm disso, se tratando de memria I/O mapeada e pginas,
o WriteMemory() pode fazer tambm o seguinte:

  Protejer a ROM contra modificaes
     Alguns softwares baseados em cartuchos (tais como os jogos de
MSX, por exemplo)  tentam escrever na  ppria ROM  e  se recusam a
trabalhar se so bem sucedidos. Isto    freqentemente feito para
se protejer a cpia.

  Manejar a memria espelhada (mirrored memory)
     Uma rea  de  memria pode  ser acessvel de vrios endereos
diferentes.  Por   exemplo,  os  dados  que  voc  escreve  dentro
localizao  $4000  tambm  pode  aparecer  em $6000 e $8000. Esta
situao  pode  ser  resolvida  com  o  uso  do ReadMemory(), mas,
usualmente, ela no    desejvel, j que o ReadMemory() obtm uma
chamada/call muito mais freqente que com o WriteMemory().
Portanto, o  caminho mais eficiente deve ser implementar a memria
espelhando na funo WriteMemory().

As  funes  ReadMemory()/WriteMemory()  do  muita  sobrecarga na
emulao, sendo que seu dever  a deixar mais  eficiente possvel,
porque elas obtero chamadas/calls muito freqentemente. Aqui  vai
um exemplo destas funes:

   leitura da memria (ReadMemory) do byte em inline esttico 
   (registra o endereo da palavra):

{
  return(MemoryPage[Address>>13][Address&0x1FFF]);
}

   leitura da memria (ReadMemory) vazia em inline esttico 
   (registra o endereo da palavra e o valor do byte):

{
  MemoryPage[Address>>13][Address&0x1FFF]=Value;
}

Note  a  palavra-chave inline.  Ela contar com um compilador para
embutir  a  funo  dentro  do  cdigo,  ao  invs de fabricar uma
chamada/call para isto.
Se seu compilador  no suportar inline ou _inline, tente  fabricar
uma  funo  esttica: alguns compiladores  (WatcomC, por exemplo)
optimizam funes estticas curtas por inlining.

Tambm, lembre-se de que na maioria dos casos, o ReadMemory() ser
chamado muitas vezes mais freqentemente que o WriteMemory().
Portanto, isto  estar valendo para implementar um cdigo maior no
WriteMemory(), guardando ReadMemory() para os mais simples e curto
possveis.

Depois que trouxer o opcode ns diminumos o contador de ciclos da
CPU pelo nmero de ciclos requeridos por este opcode: 

Counter-=Cycles[OpCode];

A  tabela  Cycles[]  dever  conter o nmero de ciclos da CPU para
cada  opcode. Tome  cuidado,  pois  alguns opcodes (tal como jumps
condicionais ou chamadas/calls do subrotina) podem tomam um nmero
diferente de ciclos dependendo  de  seus argumentos. Isto pode ser
ajustado mais tarde, com outro cdigo. 

Agora est na hora de interpretar o opcode e execut-lo:

switch(OpCode)
{

  comum  ter  a  concepo  de  que a construo de um switch() 
ineficiente  para  compilar  numa cadeia  de  declaraes if() ...
else if() ... Enquanto isso    verdadeiro para construes com um
pequeno nmero de casos, as grandes construes ( 100-200  ou mais
casos )  sempre  parecem  compilar  dentro  uma  tabela  de jumps,
deixando eles completamente eficientes.

H  dois  caminhos  alternativos  para  se interpretar os opcodes.
O  primeiro  vai  fazer  uma   tabela de  funes e chamar a  mais
apropriada. Este mtodo aparece ser menos eficiente que o switch()
pois  voc  ir receber uma sobrecarga de funes de chamada/call.
O segundo mtodo  fazer uma tabela de labels, e usar a declarao
do goto.  Enquanto  este  mtodo   ligeiramente mais rpido que o
switch(), ele  trabalhar unicamente nos compiladores que suportam 
"precomputed labels" (labels pr computadas). Outros  compiladores
no permitiro que voc crie um arranjo de endereos de labels.

Depois  da  bem  sucedida  interpretao  e execuo dum opcode, 
chegada a hora de checar se  necessria alguma interrupo. Neste
momento,  voc  tambm  pode  desempenhar  quaisquer  tarefas  que
necessitam ser sincronizadas com o relgio do sistema: 

if(Counter<=0)
{
  /* checa por interrupes e faz outra emulao de hardware */
  ...
  counter+="InterruptPeriod;"
  if(exitrequired) break;
} 

A seguir, uma  lista  curta  de  coisas  que voc pode fazer com a
declarao if():

  Checar  se  o  fim da tela    alcanado e gerar uma interrupo
    VBlank se assim
  Checar se o fim  do scanline  alcanado e gerar uma interrupo
    HBlank se assim
  Checar  por  uma  coliso de sprites, e gerar uma interruo, se
    necessrio
  Atualizar os  timers  da emulao de hardware, gerar uma
    interrupo se os timers expirarem
  Refrescar a amostra/display de scanline
  Refrescar o modo tela inteira
  Atualizar som
  Ler o estado do teclado/joysticks
  etc.

Cuidadosamente calcule o nmero de ciclos que a CPU necessita para
cada tarefa, ento use o menor nmero no InterruptPeriod, e  junte
todas  as  outras  tarefas  nele ( elas  no devem necessariamente
executar em toda expirao do contador ).

Note que ns no apenas designamos  Contador=InterruptPeriod,  mas
fizemos um Counter+=InterruptPeriod: ele far o contador de ciclos
mais preciso, mesmo usando nmeros negativos.

Tambm d uma olhada na linha

if(ExitRequired) break;

Como fica  muito  custoso checar a sada em toda passagem do loop,
ns fazemos isto unicamente quando o Contador expirar: ele  apenas
sair da emulao quando voc atribuir ExitRequired=1, no tomando
muito tempo da CPU.

Isto  tudo que eu tenho a dizer sobre emulao de CPU em C.
Voc   deve  ser   capaz  de  imaginar   quanto  trabalho  eu  lhe
economizei.

# Como eu optimizo meu cdigo C? 

.......

Mantido por Marat Fayzullin
   [fms@freeflight.com]
