Última atualização:
Console do Nintendo 8-bits e dois controles.
Console do Nintendo 8-bits e dois controles. Foto por Tomasz Filipek (https://www.pexels.com/@tombrand)

Devlog #00: Emulador NES (nintendinho), arquitetura

Nawarian
Nawarian emulador-nes

Atenção! Para que este DevLog avance eu preciso da sua ajuda não financeira: Dê retuíte neste post e compartilhe com colegas e quem mais se interessar. Somente após 50 RTs eu vou escrever o próximo post. Esta é uma forma fácil e barata de você nos ajudar o projeto codamos.com.br a crescer ao mesmo tempo que ganha um conteúdo exclusivo e de qualidade em português.

Conteúdo deste post

Há alguns bons meses eu decidi que vou escrever meu próprio emulador NES (Nintendinho 8-bits). O desafio parece interessante o suficiente e vai me manter ocupado por um tempo razoável.

De lá pra cá eu estudei e aprendi bastante coisa sobre a plataforma NES. Visitando materiais como a wiki do NESDev.com, a incrível série de vídeos do One Lone Coder e outros emuladores de código aberto.

Espero que desta vez eu consiga atingir maior sucesso e não abandone o projeto. Este Devlog vai me ajudar a tomar nota do meu progresso e não abandonar o projeto, ao mesmo tempo eu devo lançar mais conteúdo interessante pra vocês.

Eu pretendo também utilizar as ideias do Aprendizado Guiado por Testes: Introduzir, descobrir, testar, aprender, repetir. Portanto todo capítulo deste DevLog deve seguir mais ou menos esta estrutura.

Objetivo deste projeto

O objetivo maior deste emulador é conseguir rodar a ROM do jogo Super Mario Bros.. Esta ROM deverá executar com vídeo, áudio e controles.

Ferramentas utilizadas no projeto

  • Linguagem: C, padrão C99
  • Compilador: clang
  • Biblioteca de GUI: raylib
  • Ferramenta de build: Make
  • Integração Contínua: Github Actions

Eu quero MUITO desenvolver o emulador NES em PHP, mas a princípio não faz muito sentido: eu precisaria fazer muita coisa específica em PHP para desenvolver o básico que outras linguagens já oferecem. Então para esta versão, PHP não é a minha ferramenta.

Eu tenho estudado Rust e C nestes últimos 2 anos e acho que já atingi um nível de proficiência decente com as duas linguagens. A minha escolha aqui vai ser a linguagem C pela simplicidade da sintaxe. Como estou rodando um MacOS, o compilador mais confortável pra mim será o clang, mas pretendo utilizar o padrão C99 para evitar incompatibilidades.

Como engine gráfica eu poderia então utilizar o OpenGL ou SDL. Como eu quero evitar perder tempo escrevendo a minha própria abstração, vou utilizar uma biblioteca de alto nível: a raylib. Talvez numa segunda iteração eu isole melhor o tratamento de áudio e vídeo para que possa facilmente trocar a biblioteca.

Por fim, eu não imagino que o projeto vá crescer muito em número de arquivos, então uma build simples usando Make já deverá ser o suficiente.

Para garantir que nenhuma regressão inesperada aconteça, eu vou utilizar uma ferramenta de Integração Contínua. Acho que Github Actions já será suficiente para o meu caso de uso.

Visão geral: arquitetura do NES

Há três componentes essenciais em um emulador NES: CPU (unidade central de processamento), PPU (unidade de processamento de imagens), APU (unidade de processamento de áudio). Há mais elementos a serem emulados, mas estes três são os essenciais para alcançar o meu objetivo.

Estes componentes se comunicam através do barramento (BUS).

Por fim, os programas são carregados a partir de ROMs. ROMs são na realidade cópias dos programas gravados nos cartuchos.

BUS ou barramento

Este componente é o mais simples de emular porque é basicamente o acesso a memória entre os componentes.

O barramento (ou memory bus) é o que permite a CPU gravar uma sequência de bytes no endereço A e outro componente, como a PPU, ler esta sequência de bytes e interpretar como uma imagem ou algo assim.

CPU: central processing unit ou unidade central de processamento

Emular a CPU do NES será quase equivalente a emular o chip 6502. Este é o microprocessador utilizado neste console.

Este processador naturalmente possui algumas instruções que podem ser descritas na linguagem Assembly. Como LDASTAPHP entre outras. Mais detalhes no post sobre a emulação da CPU.

Dentre todos os componentes este é o mais complexo porque possui muitas instruções, modos de endereçamento e comportamentos complexos.

Um erro aqui muitas vezes é silencioso e difícil de capturar. Em alguns casos determinado bug só se materializa com uma ROM específica, em condições específicas. Quanto mais testes e cobertura tivermos aqui, melhor.

PPU: picture processing unit ou unidade de processamento de imagens

Este componente é responsável por ler trechos específicos da memória e traduzir em vídeo.

Em termos de esforço deverá ser bem menor do que a emulação da CPU, mas com certeza é o mais complexo.

APU: audio processing unit ou unidade de processamento de áudio

Semelhante ao PPU, este componente é capaz de sintetizar os áudios dos jogos: traduzir pedaços de memória em som.

Eu ainda não estudei sobre o APU, então não possui muitos detalhes sobre como este componente funciona. Vamos descobrir mais no futuro.

Visão geral da arquitetura

Com a imagem abaixo eu tento descrever o modelo que eu tenho cristalizado sobre a arquitetura do NES.

O barramento (BUS) é o que conecta todos os outros componentes.

A ROM possui um programa que é carregado para a memória, de forma que o conteúdo da ROM fica disponível no Barramento (BUS) para que outros componentes possam acessar.

A CPU é responsável por executar instruções, que são carregadas a partir de uma ROM.

A PPU é responsável por pintar na tela o estado atual da aplicação (jogo). O programa (definido na ROM) define em quais endereços de memória gravar quais bytes, e a PPU sabe exatamente quais áreas da memória acessar para pintar a tela. A APU provavelmente opera de forma semelhante.

O barramento (BUS) conecta todos os outros componentes do NES.
O barramento (BUS) conecta todos os outros componentes do NES.

Próximos passos

A visão geral foi dada, agora é preciso colocar a mão na massa.

No próximo post eu vou preparar o repositório no Github e configurar o Github Actions para executar uma build simples.

 

Comentários