Devlog #00: Emulador NES (nintendinho), arquitetura
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 LDA, STA, PHP 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.
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