[PT-BR] Importância dos Emuladores para a Preservação de Hardware - Parte 2
Added 2023-01-14 13:00:02 +0000 UTCHi! This is the part 2 of my monography "IMPORTANCE OF EMULATION FOR HARDWARE PRESERVATION", which I used for getting my bachelor degree at computer engineering. This part focuses on reverse engineering techniques, different emulation methodologies and the challenges of system emulation. The part 1 is available here.
This is the original, brazilian portuguese version of the paper. The paper also had the orientation of Prof. Guilherme Guindani, Ph.D and was evaluated by Prof. Laercio Carpes, MSc.
The english version is work in progress and will be made available soon.
Prefácio
Olá! Esta é a parte 2 da minha monografia "IMPORTÂNCIA DOS EMULADORES PARA A PRESERVAÇÃO DE HARDWARE", na qual foi requisito para finalizar o meu curso de bacharelado em engenharia de computação. Esta parte foca nas técnicas de engenharia reversa, nas diferentes metodologias de emulação e os desafios da emulação de um sistema. A parte um está disponível aqui.
Esta é a versão original, escrita em português brasileiro. O trabalho teve orientação do Prof. Guilherme Guindani e foi avaliado pelo Prof. Laercio Carpes.
A versão em inglês está sendo feita e estará disponível em breve.
5 Tipos de emuladores e estratégias de criação
Os capítulos anteriores tiveram como objetivo de contextualizar de forma generalizada os tipos de hardware (console e periféricos) que existem e a interação que realizam com os softwares que foram lançados visando os recursos do hardware. O desafio é permitir que esses softwares consigam sobreviver ao processo de descontinuação dos sistemas que suportam esses softwares. Todo o hardware irá eventualmente falhar ou tornar-se comercialmente inviável de continuar sua produção ou seu uso, portanto, é necessário o uso de emuladores para permitir que esses softwares, lançados originalmente para um hardware específico, continuem funcionando.
O processo de criação de um emulador é relativamente complexo. Sem uma descrição de hardware adequada, ou documentação, levará muitos anos até atingirmos um ponto de maturidade em relação a emulação de um determinado sistema. O Super Nintendo, por exemplo, levou mais que 20 anos até que foi possível emular todos os jogos lançados oficialmente para o console (Near, 2020).

Figura 17 – Jogo Super Mario World (Super Nintendo) sendo executado no emulador bsnes em um sistema operacional baseado no Linux (Evan G, 2010).
Apesar disso, utilizando diferentes técnicas, é possível obter uma emulação satisfatória e funcional em um prazo menor, permitindo com que seja possível utilizar os principais softwares enquanto ainda não há conhecimento suficiente ou poder computacional para emular um determinado sistema. Essa é uma estratégia válida e que diversas companhias fazem, para que um conjunto de softwares continuem comercialmente viáveis, sem depender de um hardware já descontinuado. Nos últimos anos vimos alguns jogos sendo relançados onde na essência o usuário recebe um emulador configurado para executar apenas um jogo em específico, e o jogo possui alguns trechos intencionalmente alterado para interagir com a API do emulador, assim criando uma experiência imersiva em um computador moderno.
Enquanto o bsnes, emulador criado pelo Near em 2004, para rodar em um computador, precisa de um processador Intel Core com uma frequência de cerca de 3 GHz, o ZSNES, um dos primeiros emuladores criados para Super Nintendo, consegue executar o mesmo jogo em um Intel Pentium de 300 MHz. Isso se deve a estratégias diferentes adotadas pelos emuladores (Near, 2011).

Figura 18 – Jogo Super Mario World (Super Nintendo) sendo executado no emulador ZSNES em um sistema operacional baseado no Linux (Evan G, 2010).
Durante muito tempo, houve uma grande preferência pelo emulador ZSNES, justamente por ser capaz de emular os mesmos jogos do Super Nintendo, porém com um desempenho consideravelmente superior. Escrito em Assembly baseado na arquitetura x86, o ZSNES tem o foco em emulação funcional, otimizando as operações na medida do possível e renunciando a algumas características do hardware original para que o desempenho seja otimizado para qualquer computador pessoal. Até meados de 2012, foi considerado o emulador mais popular, porém com a falta de atualizações e problemas envolvendo recursos mais avançados do Super Nintendo, que causava incompatibilidades com alguns jogos e, especialmente, com mods, o emulador começou a ser preterido pelo Snes9x e pelo bsnes, que utilizam outras abordagens de emulação.

Figura 19 – Jogo Super Mario World (Super Nintendo) sendo executado no emulador Snes9x em um sistema operacional baseado no Linux (Evan G, 2010).
O Snes9x também foi um dos primeiros emuladores criado para o Super Nintendo, com foco inicialmente similar com o ZSNES. O emulador é desenvolvido em C++ e desde que o trabalho do Near com o bsnes foi ganhando destaque, o Snes9x começou a renunciar a algumas características em nome de uma maior compatibilidade com jogos e maior qualidade e fidelidade de imagem e som, algo que é um problema no ZSNES, visto que todos os jogos apresentam uma qualidade de imagem e som ligeiramente inferior em comparação com um Super Nintendo real. O Snes9x balanceava entre uma emulação de alto nível, sem deixar qualidade em segunda mão, utilizando algumas técnicas de emulação de baixo nível e apurada, bastante próxima do hardware, equilibrando desempenho no computador e fidelidade na emulação do sistema.
5.1 Emulação de alto nível
O primeiro passo para construir um emulador envolve o conhecimento de alto nível do sistema. Quase todos os componentes são uma caixa preta, com pouco ou nenhum detalhe do funcionamento de cada um. Por conta disso, não é possível de imediato criar um emulador de baixo nível, isto é, uma emulação já levando em conta todos os detalhes do sistema, pois provavelmente as interações entre componentes não funcionará corretamente. Logo, a emulação funcional, de alto nível, é o primeiro passo que podemos iniciar.
A emulação de alto nível (high level emulation – HLE) tem como objetivo simplificar as operações que um emulador precisa fazer para o software funcionar. Ao invés de preocupar com características de timing, ciclos, frequência, restrições (constraints) ou operações indefinidas, o HLE se preocupar apenas na entrada e saída de dados (I/O – input and output) entre dois componentes, que pode ser a interação entre dois circuitos integrados, ou a interação entre um periférico e um circuito, ou a interação entre um componente e o software (APIs ou registradores). Na prática, é feito a abstração de alguns detalhes e peculiaridades do hardware e foca-se no resultado na interação entre software e hardware a fim que seja exibido uma imagem e som, nem que seja uma aproximação do resultado.
Uma primeira etapa que podemos observar ao construir um emulador de alto nível é observando a placa de um hardware. Apesar de não conhecermos o que cada chip faz exatamente, podemos ver a disposição de todos os chips, as conexões entre cada chip, como barramentos, trilhas e alimentação. É possível inferir o papel de cada um observando as conexões entre os chips, as entradas do hardware, as saídas do hardware, as conexões entre componentes elétricos e as informações preliminares que temos em cima de datasheets dos circuitos utilizados, junto com quaisquer manuais técnico que possam estar acompanhados com o hardware.

Figura 20 – Diagrama funcional do sistema do Super Nintendo, com cada um dos módulos separados. O diagrama foi feito em cima da placa mãe da primeira versão do Super Nintendo (Near, 2020).
Na figura 20 é possível visualizar o diagrama do Super Nintendo. As conexões pontilhadas entre os blocos significam uma conexão entre um componente e um chip de memória, enquanto uma conexão direcionada indica as direções que cada componente pode interagir. Podemos imaginar cada componente sendo uma caixa preta, onde enquanto o funcionamento interno é completamente desconhecido, as suas interações (conexões) são conhecidas. Sabemos como a CPU principal do Super Nintendo interage com a CPU de áudio (SMP – Sony SPC700) e a partir disso podemos testar a interação entre esses dois circuitos para que assim seja possível entender o funcionamento e a interação de cada chip.
Também a partir desse diagrama e das informações disponíveis na placa mãe podemos descobrir mais informações sobre alguns componentes. Não sabemos exatamente o que os chips PPU-1 e PPU-2 fazem, porém sabemos que a CPU principal é um chip da Western Design Center, um processador 65c816 de 16-bit CISC, lançado em 1985 e presente em alguns computadores como o Apple IIGS (Neil P, 2004). Este chip possui um datasheet disponível, com detalhes sobre o conjunto e instruções e características, onde a partir desse ponto podemos inferir diversas informações e criar um emulador que faça a emulação em alto nível desse chip. Apesar dos pinos entre o chip original e a versão modificada do Super Nintendo não serem compatíveis, com base nas informações iniciais podemos criar uma emulação mínima em cima do componente e através de realização de experimentos com o hardware original, o processo de engenharia reversa torna-se mais simples de realizar.
O mesmo processo pode ser feito com o chip SMP. O chip é uma versão personalizada do Sony SPC700 series, uma CPU baseado na arquitetura 6502 de 8-bits. Com as informações encontradas em cima do datasheet, podemos também compreender o conjunto de instruções e criar um modelo inicial de emulação do componente em específico (Near, 2011).
Como o Super Nintendo foi um videogame com o desenvolvimento aberto para desenvolvedores terceiros, a Nintendo disponibilizou também um documento oficial de desenvolvimento do Super Nintendo, chamado de SNES Development Manual. Apesar de ser restrito para desenvolvedores, o manual foi arquivado e está disponível para consulta na Internet. A quantidade de detalhes disponível no documento permite a compreensão a nível funcional de todos os componentes relacionados, onde apesar de não mencionar detalhes de registradores ou técnicas utilizadas em alguns jogos, permite conhecer o sistema em um grau de profundidade suficiente para desenvolver testes de mesa que sejam capazes de compreender o restante das características do sistema.

Figura 21 – Exemplo de um teste de mesa (John, 2020).
Testes de mesa são uma maneira de validar se um hardware está funcionando corretamente. Dado os pinos de entrada e saída de um componente, e o comportamento esperado após um conjunto de entradas e saídas, o teste de mesa consegue estimular o circuito ou o componente e validar se as entradas estão gerando as saídas corretas. Isso auxilia o processo de desenvolvimento de um hardware para que falhas sejam encontradas antes de fato criar o hardware físico. Com um hardware já pronto, podemos utilizar o processo de teste de maneira reversa, de forma que usando entradas que já conhecemos, estimular o hardware físico e gerar um conjunto de saídas para que seja feito uma compreensão melhor de como o hardware funciona.
Como é possível executar um software dentro do sistema, é possível automatizar tanto a coleta das saídas e a validação dos testes, podendo assim um desenvolvedor criar softwares que testam o hardware, validam teorias e coletem informações que serão importantes para compreender melhor o sistema. Junto com as informações já conhecidas em documentos e datasheets, o desenvolver pode validar se um conjunto de entradas no emulador gera as mesmas saídas esperadas pelo hardware, assim podendo validar se um emulador está gerando os resultados corretamente. Como um sistema complexo possui centenas de registradores e interações, o teste de mesa reverso permite desenvolver um emulador de alto nível de maneira eficaz, conhecendo as entradas e saídas, aplicando o funcionamento no emulador e validando se o funcionamento está similar com o hardware a ser emulado.

Figura 22 – Exemplo de teste de mesa reverso onde coleta informações de um hardware ou componente e depois compara os resultados com o emulador.
O processo funciona através de tentativa e erro. Caso o resultado do teste não seja igual com as saídas esperadas, é necessário investigar minuciosamente o resultado e ir remodelando o emulador até que as saídas esperadas sejam produzidas. O processo de desenvolvimento será, portanto, similar ao TDD (Test Driven Development), um processo de desenvolvimento ágil onde se escreve primeiro os testes e os resultados esperados antes de prosseguir com o desenvolvimento do software.
Por fim, importante destacar que a técnica pode não funcionar corretamente em componentes de áudio e vídeo, especialmente em componentes responsáveis pela de produção de sinais analógicos, devido a impossibilidade de realizar testes automáticos. Para estes casos, é necessário realizar inspeções manuais e em pequenas amostras, o que demandará um esforço maior e atenção aos pequenos detalhes que podem passar despercebidos a olho nu.
5.2 Emulação de baixo nível
Quando eventualmente há um conhecimento mais aprofundado de um determinado sistema, torna-se viável começar a implementar módulos que operam em um nível de detalhes mais aprofundado, que realizam funções praticamente idênticas em relação ao hardware original. Conhecido como emulação de baixo nível (low level emulation, LLE), a técnica tornou-se necessária para aumentar a compatibilidade entre softwares que apesar de utilizarem o mesmo hardware, não funcionam no emulador devido a características ou comportamentos peculiares em que o hardware original dispõe, mas o emulador não.
É importante deixar claro que não necessariamente um emulador que usa a técnica HLE esteja incorreto. As entradas e saídas em testes com o hardware podem estar corretas, mas os constraints e o timing não. Em outras palavras, violações em relação a restrições de timing ou setup time podem ser a causa em relação a discrepância entre um hardware e o emulador. Também pode acontecer de certas limitações do hardware em cenários específicos gerarem resultados distintos. Ou seja, apesar do resultado teórico estar correto, devido a alguma limitação do design do hardware, é recebido um outro resultado.
Violações de contrato e de timing são mais comuns em registradores e recursos do processador. Por exemplo, a CPU do Super Nintendo, o 65C816, não possui opcodes(comandos) que realizam multiplicação ou divisão de números. Por conta disso, o S-CPU inclui alguns registradores especiais (neste contexto, estão disponíveis como endereços específicos dentro do endereçamento da CPU). Basta armazenar os dois números nestes registradores e dentro de alguns ciclos e o resultado é registrado em um determinado endereço. Porém, o que acontece se o software tentar ler o resultado antes do tempo estipulado por estes registradores?
Muitos jogos de Super Nintendo tinham resultados diferentes entre o hardware e o emulador devido a esse motivo. Não aguardaram totalmente o resultado de uma multiplicação ou divisão, e por conta disso, receberam resultados parciais do circuito de multiplicação e divisão do processador. Esses resultados parciais acabaram passando por despercebido e o comportamento ou movimentação de um personagem ficou agindo conforme os resultados parciais. Quando esse mesmo jogo é executado em um ambiente de emulação, um resultado diferente é gerado e por conta disso é registrado um comportamento incomum, que pode impactar a experiência do jogo. Em casos extremos, esses resultados diferentes podem causar um travamento ou uma exceção no jogo, impedindo o funcionamento correto.
Muitos dos problemas afetam jogos específicos, logo é comum um emulador não perceber esse detalhe quando há uma biblioteca grande jogos. Um exemplo disso é o jogo F-1 Grand Prix, que utiliza o mecanismo de interrupção do SNES (IRQ – Interrupt Request) durante a renderização para alterar um registrador específico do chip gráfico do sistema. Este registrador altera a maneira que é renderizado a HUD do game. Como é uma operação feita apenas por esse jogo, os emuladores não sabem como lidar com a mudança abrupta do registrador e ignoram a alteração, gerando uma diferença entre o hardware e o emulador.

Figura 23 – Imagem do jogo F-1 Grand Prix (SNES), com a HUD exibindo corretamente na parte direita da tela (Near, 2011).

Figura 24 – Imagem do jogo F-1 Grand Prix (SNES), com a HUD exibindo incorretamente na parte direita da tela (Near, 2011).
Existem situações em que o software enviou os dados corretamente, mas o hardware não sabe lidar corretamente, seja por limitações de velocidade ou por uma implementação incompleta de algum módulo. Um exemplo comum no Super Nintendo é durante a fase de decodificação de um bloco de áudio. Durante a decodificação, onde utiliza operações de multiplicação com soma acumulativa, em algumas entradas pode ocorrer um estouro no acumulador, que é 16-bit, resultando em um áudio com um som ligeiramente “pipocado”. Como muitos emuladores fazem essa operação, sem perceber, em 32-bit (int), antes de truncar para 16-bit, esse comportamento não é reproduzido, tornando alguns sons, em particular efeitos sonoros, diferentes do hardware original. Uma emulação de baixo nível, tomando cuidado durante cada operação certificar de que o registrador estará no tamanho e no formato correto, impede cenários como esse.
Implementações parciais ou de alto nível de alguns componentes também resultam em problemas. Conforme citado no capítulo 4.1, existem alguns softwares que vem com um hardware adicional. Um exemplo desse hardware é o chip SA-1 (Super Accelerator 1 ou superacelerador). O chip é baseado no processador original do Super Nintendo, o 65c816, porém com melhorias no desempenho e em circuitos matemáticos, podendo executar o mesmo software que o processador principal do SNES até 6x mais rápido. Um recurso em particular do chip é a capacidade de transformar gráficos para o formato do chip gráfico do Super Nintendo em tempo real, sincronizado dois DMAs (Direct Memory Access – acesso direto a memória) do chip SA-1 com a CPU primária do SNES. Alguns emuladores não implementaram o recurso, fazendo com que alguns jogos que utilizavam o recurso não exibissem corretamente no emulador.

Figura 25 – Jogo SD Gundam G-Next exibindo os mapas corretamente, através de um recurso do chip SA-1 (Near, 2011).

Figura 26 – Jogo SD Gundam G-Next exibindo os mapas incorretamente, por falta de suporte adequado ao chip SA-1 (Near, 2011).
Um outro motivo para o uso da emulação de baixo nível é após pessoas perceberem que, ao efetuar certas melhorias em um emulador, para fim de corrigir um bug ou um comportamento incorreto em um software A, este comportamento acabou prejudicando o funcionamento do software B. Por sua vez, ao consertar um detalhe que afetava o software B, o software A volta a não funcionar corretamente. Como dois softwares feitos para o mesmo hardware esperam comportamentos diferentes em um emulador?
Inicialmente, alguns autores defenderam a criação de emuladores específicos para certos jogos, ou o uso de checagens específicas para identificar qual software está sendo executado e dependendo do caso, aplicar comportamentos diferentes dentro de um mesmo recurso do hardware. Funciona muito bem para possibilitar a execução do software, mas a longo prazo notou que no ponto de vista de preservação e desenvolvimento não era uma opção boa, visto que: 1) ninguém compreenderia qual era exatamente o comportamento esperado; 2) softwares que fossem produzidos em cima de um emulador poderiam não funcionar após inserir no hardware; e 3) tornava a manutenção e a evolução dos emuladores cada vez mais complexa, especialmente dado que um mesmo sistema poderia ser utilizado por mais de 3.000 softwares diferentes. Near percebeu isso no começo do desenvolvimento do bsnes e aboliu esse tipo de prática em seu emulador (2011).
Assim para o caso da emulação de alto nível (HLE), o teste de mesa também é um dos meios válidos para validar uma emulação de baixo nível (LLE). Nestes testes em específico, além de validar as entradas e saídas, os testes precisam também avaliar as informações de timing, como setup time e as constraintsde cada função. Na prática, os testes precisam provocar situações inconvenientes para o hardware, focando os testes nos cenários mais improváveis e incorretos. Exemplos: 1) O que acontece se tentar executar uma função DMA em um endereço de memória inválido? 2) O que acontece se o software tentar realizar uma divisão por zero? 3) O que acontece se o software tentar exibir mais sprites (pequenas imagens) do que o limite que o chip gráfico permite? e 4) O que acontece se o software tentar acessar a VRAM (vídeo RAM) enquanto o chip gráfico está acessando a memória no mesmo ciclo?
Testes em cima de cenários do gênero implica em testar os limites do hardware, o que demanda cuidado adicional para evitar danos caso os testes impliquem em utilizar sinais elétricos inválidos, como um curto-circuito ou uma sobretensão. Nestes cenários, vale conferir se o datasheet menciona algum detalhe sobre operações fora do padrão indicado.
Por fim, existem casos em que simplesmente não é possível compreender os resultados, ou não há onde aferir os mesmos. Pode ser devido a um processo intermediário de um componente, comum em circuitos sequências, onde sem conhecer esses valores intermediários e os valores dos registradores escondidos, a compreensão dos resultados fica comprometida. No próximo capítulo algumas alternativas são discutidas, que na prática envolve processos demorados com custo altíssimo, porém são eficientes em compreender um sistema como todo.
6 Desafios da engenharia reversa
Especialmente em emuladores de baixo nível, existem situações em que simplesmente não é possível compreender os resultados de um teste e a documentação, como datasheet dos circuitos e guia de desenvolvimento do hardware são insuficientes para construir um emulador completamente apurado. Este é um dos problemas quando não existe mais a descrição de hardware disponível dos componentes, onde há diversas camadas de circuitos combinacionais e circuitos sequenciais sem ter como aferir o comportamento de um sistema com exatidão. Também existem máquinas de estado internas de um circuito integrado, onde o software que está sendo executado não terá ciência da existência desses estados internos, exceto caso o estado esteja exposto através de um registrador externo.
Quando não é possível realizar testes no hardware através de um software, seja automático ou manual, podemos utilizar algumas outras técnicas para compreender melhor o funcionamento do hardware. Uma opção é abrir o chip e analisar, através de uma imagem de altíssima resolução, quase microscópica, as estruturas dentro do chip, diretamente no die, procurando por alguma informação que possa ser relevante para a compreensão do chip.

Figura 27 – Visualização do die do chip S-PPU1, amplificado em 20x, feito pelo John McMaster (Near, 2020).
Uma visualização desse nível do chip pode ajudar a compreender melhor o funcionamento do chip. Podemos tirar, de conclusões, por exemplo, que o chip não aparenta ser uma CPU conhecida, também não aparenta ser algum tipo de arquitetura que opera algum software (firmware) intermediário. É possível concluir que o chip é feito de vários circuitos combinacionais e sequenciais, completamente personalizado para uma determinada função específica.
Com uma imagem expandida não podendo ser suficiente para encontrar resultados, o próximo passo seria utilizar um tipo de osciloscópio chamado logic analyzer(analisador lógico).

Figura 28 – Um osciloscópio exibindo sinais a partir de um circuito (Near, 2020).
Se não podemos compreender completamente um chip observando os resultados visuais ou resultados que seus registradores externos exibem para o software, é possível conectar todos os pinos para um osciloscópio e tirar uma grande amostra de resultados em cima de qualquer teste que fizer. Isso permite compreender como todos os pinos se comportam, porém requer um nível de esforço muito grande e ainda impede validar esses resultados de maneira automatizada.
Uma outra opção é remover o chip da placa mãe do Super Nintendo, colocar em uma protoboard e conectar todos os pinos de dados a um circuito lógico programável (PLD), por exemplo um FPGA. Em seguida, seria necessário sintetizar um código HDL que fosse interagir exclusivamente com o chip PPU, realizando diversos tipos de teste para compreender a um nível mais próximo o funcionamento do componente. Seria uma maneira bastante complicada de fazer, seria necessário levar em conta os níveis de tensão, capacitância, pinos analógicos, fora configurar centenas de pinos para que o FPGA fique conectado com o chip. Por lidar com centenas de pinos, seria necessário também monitorar, de ciclo em ciclo, o comportamento de cada operação do chip, gerando resultados gigantescos e possivelmente tendo que modificar o emulador para que seja possível comparar e validar esses resultados.
A opção mais efetiva, e extrema também, seria realizar o decapping do chip. Na essência, gerar uma imagem tão grande do chip ao ponto de conseguirmos visualizar cada transistor individualmente. Podendo visualizar cada transistor de maneira individual, seria possível montar do zero cada porta lógica, cada circuito combinacional, cada componente do chip até chegarmos no ponto em que vamos conseguir gerar uma nova descrição de hardware em cima do chip. Um processo que é considerado muito demorado e caro de se realizar.

Figura 29 – Zoom na escala de 1 nm do chip S-PPU1 (John McMaster, 2022).
7 Desafios da emulação apurada
A emulação apurada, que emprega todas as técnicas de emulação de baixo nível e precisa é sem dúvidas uma das melhores maneiras de preservar um hardware, pois além de trabalhar com um comportamento praticamente idêntico em relação ao sistema original, ela também se preocupa em garantir a compatibilidade com todos os periféricos, softwares e sistemas intermediários sem prejuízo na qualidade e fidelidade que o hardware original emprega. Entretanto, a emulação apurada possui diversos desafios que podem deixar a mesma simplesmente inviável de criar ou utilizar, devido ao nível de detalhes necessário para trabalhar.
Um emulador apurado irá consumir muito mais recursos do que um emulador comum, pois devido às características de timing e aos detalhes, o programador é obrigado a efetuar cada etapa de emulação de uma maneira bem minuciosa, demandando recursos computacionais elevadíssimos. O Super Nintendo, um sistema que o processador base roda a 3.58 MHz, precisa de um processador que roda a 3 GHz para conseguir emular através do bsnes, um emulador apurado. O mesmo sistema pode ser emulado com um processador rodando a 300 MHz através do ZSNES (Near, 2011).
Um dos motivos para a discrepância é a necessidade de sincronização. Um exemplo é quando há dois processadores que podem interagir entre si. Vamos chamar de processador A e B. Apesar de possuírem relógios diferentes, ambos derivam de um mesmo cristal oscilador, portanto operam de forma sincronizada. Um emulador, dentro das boas práticas da engenharia de software, irá dividir os processadores A e B em diferentes componentes. Cada vez que o processador A for emulado, um conjunto de instruções será executado. Ao finalizar a emulação das instruções fornecidas, o programa irá emular o processador B, executando a quantidade de instruções necessárias até que seja finalizado o bloco atual de emulação.
Dois processadores paralelos são obrigados a serem emulados de maneira sequencial. Por mais que seja possível paralelizar a execução deles, há um problema grave nisso – há uma interação entre os processadores e o timing precisa ser exatamente igual ao hardware original, caso contrário haverá comportamentos e desempenhos diferentes entre o emulador e o hardware. Devido a diferenças de arquiteturas, tanto no ponto de vista de hardware e no ponto de vista do sistema operacional, afinal este emulador muito provavelmente será executado dentro de um SO, simplesmente é inviável deixar a sincronização a cargo do sistema operacional ou do processador do computador, pois os mesmos irão priorizar o sistema host, não o sistema que está sendo emulado.
Além disso, enquanto a emulação do processador A estiver sendo executada, o processador A poderá interagir com o processador B, para saber o status de execução de uma tarefa ou até mesmo interromper o processador B (ou vice-versa). Assim, cada vez que passa um ciclo do processador A, seria obrigatório mudar o contexto e emular um ciclo do processador B, para que ambos consigam se interagem a cada ciclo e dentro do mesmo instante de tempo, como seria no hardware original. O desempenho do emulador seria horrível, visto que haveria constantemente troca de contexto entre o processador A e B (que estão sendo emulados), assim invalidando completamente o cache do processador (do sistema host) que está executando o emulador e gerando acessos longos para efetuar uma operação a nível de ciclo.
Uma solução seria utilizar um mecanismo inteligente que detecta quando o processador A irá interagir com o processador B, de forma que somente haja troca de contexto quando realmente for necessário. Caso ambos os processadores fiquem um tempo sem interagir, é possível emular milhões de ciclos sem precisar trocar de contexto, assim tornando a emulação muito mais eficiente para o processador que esteja executando o emulador.
Uma outra solução, cada vez mais comum, é utilizar FPGAs. Por ser uma arquitetura completamente paralela, é possível de ciclo a ciclo interagir com qualquer quantidade de processadores que for necessário e não haverá penalidade de desempenho, pois o FPGA estará, normalmente, operando na frequência do circuito de maior frequência. Como o desenvolvimento de um emulador através do FPGA envolve o uso de uma linguagem de descrição de hardware, o autor é obrigado a pensar no funcionamento ciclo a ciclo do hardware, e a nível de circuitos combinacionais e sequenciais, criando um emulador a nível de porta lógica, que possui potencial de ser ainda mais apurado do que um emulador com precisão a nível de relógio.
Um possível problema envolvendo FPGAs é quando envolve processadores que possuem frequências completamente (não são múltiplos entre si) diferentes e nenhum deles é sincronizado. Para piorar, ambos interagem utilizando latch ou portas I/O assíncronas, tornando o comportamento bem indefinido de se emular até mesmo usando FPGAs. Um outro problema também envolve os próprios componentes dos kits FPGAs. Enquanto um hardware pode estar utilizando uma memória SRAM, que não há período de refresh, um FPGA poderá ter que lidar com uma memória DRAM, que possui uma latência e um período de refresh, dificultando o processo de desenvolvimento do emulador de hardware.
Em geral, questões que são influenciadas por sinais e componentes elétricos possuem comportamentos difíceis de emular. Isso porque a carga atual do circuito, a tensão da fonte de alimentação e o tempo de propagação dos sinais elétricos são muito difíceis de serem reproduzidos.
Para piorar, nem todos os processadores utilizam cristais osciladores, que possuem uma precisão altíssima na geração de sinal de relógio (frequência). Existem hardwares que utilizam cristal de cerâmica, uma opção mais acessível de geração de sinal de relógio, porém bem mais imprevisível. Dependendo das condições de fabricação e temperatura ambiente, o cristal pode apresentar até 5% de diferença para mais ou para menos. Assim, cada hardware fabricado poderá comportar de maneira diferente.
No caso do Super Nintendo, existem jogos que irão, inclusive, falhar se o cristal estiver muito fora da frequência padrão, por mais que seja algo esperado. Para isso, por mais que seja conhecida a frequência nominal, os emuladores são obrigados a utilizar a frequência mais comum encontrada, na média dos hardwares utilizados e certificar que aquele valor específico não afetará alguns software específicos que utilizam uma comunicação assíncrona entre dois processadores com relógios diferentes.
Uma outra questão envolve direitos autorais. Para o caso em que o hardware que estiver sendo emulado possuir firmware ou BIOS, que controlam certos componentes eletrônicos, se a empresa que estiver desenvolvendo o software não tiver os direitos autorais em cima desses softwares secundários, a emulação de baixo nível simplesmente será inviável. Nesses casos, é necessário substituir a emulação de baixo nível por uma emulação de alto nível, apurada dentro do possível, que faça as mesmas funções que o chip e o firmware fariam, mas no ponto de vista funcional apenas. É necessário que o componente de alto nível tenha o mesmo protocolo de comunicação do que o emulador de baixo nível, para que seja possível interagir com outros componentes de baixo nível. Uma alternativa a isso seria disponibilizar o componente de baixo nível, mas o usuário do emulador seria obrigado a além de ter o software a ser emulado, também tenha em mãos todas as BIOS e firmware necessários para executar o software.
Por fim, uma questão mais pragmática é a variação de versões entre o sistema que está sendo emulado. Existem pelo menos 3 versões do Super Nintendo (Near, 2011), que contém revisões de hardware com correções importante de bugs. Em particular, existe um bug na primeira versão do Super Nintendo onde caso um DMA é configurado no mesmo ciclo de um HDMA, um recurso de hardware do Super Nintendo que atualiza o chip gráfico em determinados scanlines, a CPU irá travar. Um emulador, que normalmente utilizará a versão mais recente do Super Nintendo, não reproduzirá esse bug, que talvez seja de interesse de alguns desenvolvedores preocupados em garantir compatibilidade com a primeira versão do Super Nintendo. Logo, nem sempre os emuladores poderão tratar todos os cenários possíveis, ainda mais quando há revisão de alguns dos chips utilizados na placa de um sistema.
Nota ao leitor
Esta é a parte 2 do trabalho. A parte 1 está disponível neste link: https://www.patreon.com/posts/pt-br-dos-para-1-76956281. A conclusão e as referência bibliográficas abaixo se referem ao trabalho de conclusão de curso completo.
CONCLUSÃO
Um software normalmente é projetado para um determinado sistema, que possui arquitetura e hardware próprios. Conforme a tecnologia vai evoluindo, esse sistema pode não ser mais viável para a execução do software, portanto precisamos pensar em maneiras de preservar esse software, para que dentro de alguns anos, décadas ou até séculos que seja possível executar o software e tenha a mesma experiência do que no momento do lançamento. A emulação é uma ótima maneira de garantir que esse software poderá se preservado digitalmente, sem se preocupar com a degradação do sistema original ou com a obsolescência de dispositivos ou periféricos que dependem do software.
Caso não haja a descrição de hardware original, o grande desafio é a realização da engenharia reversa para a criação do emulador. Como um sistema normalmente está atrelado a um número grande de softwares, é um processo que vale a pena ser realizado e que demandará menos esforço do que a migração do software para um sistema e uma arquitetura nova.
É importante garantir que o emulador seja ligeiramente apurado, para que seja possível executar o software com a qualidade e fidelidade mais próxima ao hardware. Nem sempre isso será possível, porém no ponto de vista de compatibilidade, um bom emulador será suficiente para preservar este software, e assim permitindo que o mesmo continue disponível para ser utilizado.
Os exemplos foram aplicados principalmente para o console Super Nintendo, porém as técnicas e as situações explicadas podem ser aplicadas para qualquer hardware lançado.
REFERÊNCIAS BIBLIOGRÁFICAS
Andreas L. Discovering New Paths Playing Old Games. Bibliotheksdienst, vol. 54, no. 5, 2020, pp. 313-344. Disponível em: <https://www.degruyter.com/document/doi/10.1515/bd-2020-0044/html>. Acesso em: 27 nov. 2022.
Anomie. Schematics, Ports, and Pinouts. Disponível em: <https://wiki.superfamicom.org/schematics-ports-and-pinouts>. Acesso em: 3 dez. 2022.
Aurerio. Plug and Blast: Super Multitap. Disponível em: <https://www.nintendoblast.com.br/2012/04/plug-and-blast-super-multitap.html>. Acesso em: 3 dez. 2022.
Bernadette J. How Abandonware Works. Disponível em: <https://computer.howstuffworks.com/abandonware.htm>. Acesso em: 1 dez. 2022.
Eric G. From Floppies to Solid State: The Evolution of PC Storage Media. Disponível em: <https://www.pcmag.com/news/the-evolution-of-pc-storage-media>. Acesso em: 30 nov. 2022.
Eric W. Computer Simulations in Science. The Stanford Encyclopedia of Philosophy. Disponível em: < https://plato.stanford.edu/archives/sum2013/entries/simulations-science/>. Acesso em: 27 nov. 2022.
Evan G. Mash Mods SNES Retro Flash Programmer/Retro Flash Cart. Canadá. Disponível em: <https://snescentral.com/article.php?id=0993>. Acesso em: 1 dez. 2022.
Evan G. SNES Chip List. Disponível em: <https://snescentral.com/chiplisting.php>. Acesso em: 1 dez. 2022.
Evan G. Star Fox. Disponível em: <https://snescentral.com/article.php?id=0636>. Acesso em: 3 dez. 2022.
Evan G. State of SNES Emulation - 2010. Disponível em: <https://snescentral.com/article.php?id=0995>. Acesso em: 30 nov. 2022.
Evan G. Super NES Technical Specifications. Disponível em: <https://snescentral.com/article.php?id=0088>. Acesso em: 1 dez. 2022.
Evan G. Super Scope. Disponível em: <https://snescentral.com/article.php?id=0017>. Acesso em: 3 dez. 2022.
Jeffrey H. Emulation for Digital Preservation in Practice: The Results. The National Library of the Netherlands, Holanda. Disponível em: < http://www.ijdc.net/article/view/50>. Acesso em: 27 nov. 2022.
John E. The internal workings of video game consoles: The GameBoy. Mälardalen University, Suécia. Disponível em: <https://www.diva-portal.org/smash/get/diva2:433485/FULLTEXT01.pdf>. Acesso em: 27 nov. 2022.
John McMaster. s-ppu1-5c77-01. Disponível em: <http://www.siliconpr0n.org/map/nintendo/s-ppu1-5c77-01/>. Acesso em: 4 dez. 2022.
John McMaster. s-ppu2-5c78-01. Disponível em: <http://www.siliconpr0n.org/map/nintendo/s-ppu2-5c78-01/>. Acesso em: 4 dez. 2022.
John. How to Write a Basic Verilog Testbench. Disponível em: <https://fpgatutorial.com/how-to-write-a-basic-verilog-testbench/>. Acesso em: 3 dez. 2022.
Koninklijke Bibliotheek. What is emulation?Disponível em: < https://web.archive.org/web/20110908070810/http://www.kb.nl:80/hrd/dd/dd_projecten/projecten_emulatiewatis-en.html>. Acesso em: 27 nov. 2022
Lucas R. Star Fox (SNES) e o Super FX. Disponível em: <https://jogoveio.com.br/starfox/>. Acesso em: 3 dez. 2022.
Near. 2011-02-28 - Why Accuracy Matters. Disponível em: <https://helmet.kafuka.org/accuracy/>. Acesso em: 3 dez. 2022.
Near. Accuracy takes power: one man’s 3GHz quest to build a perfect SNES emulator. Disponível em: <https://arstechnica.com/gaming/2011/08/accuracy-takes-power-one-mans-3ghz-quest-to-build-a-perfect-snes-emulator/>. Acesso em: 1 dez. 2022.
Near. How SNES emulators got a few pixels from complete perfection. Disponível em: <https://arstechnica.com/gaming/2021/06/how-snes-emulators-got-a-few-pixels-from-complete-perfection/>. Acesso em: 1 dez. 2022.
Near. SHVC-1C0N5S-01. Disponível em: <https://snescentral.com/pcbboards.php?chip=SHVC-1C0N5S-01>. Acesso em: 1 dez. 2022.
Neil P. The 6502/65C02/65C816 Instruction Set Decoded. Disponível em: <https://llx.com/Neil/a2/opcodes.html>. Acesso em: 2 dez. 2022.
Sam B. Nintendo announces mini Super Famicom for Japan. Disponível em: <https://www.theverge.com/2017/6/26/15878004/nintendo-super-famicom-mini-japan-price-release>. Acesso em: 2 dez. 2022.
Stewart Granger. Emulation as a Digital Preservation Strategy. University of Leeds, Reino Unido. Disponível em: <http://www.dlib.org/dlib/october00/granger/10granger.html>. Acesso em: 27 nov. 2022.
UTMEL. The Evolution History of Storage Devices. Disponível em: <https://www.utmel.com/blog/categories/memory%20chip/the-evolution-history-of-storage-devices>. Acesso em: 2 dez. 2022.