Construindo o seu próprio emulador, parte 1
Tags: chip8, emuladores
Published: 2021-10-24 12:03:56

---
title: Construindo o seu próprio emulador, parte 1
published: true
description: Entendendo e construindo um chip-8
tags: chip8, emuladores
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdvncg9ac7o96tmaogii.png
---

## O que é um emulador?

Um emulador emula hardware em software. É uma técnica que habilita um computador **imitar as características de outro hardware**, como por exemplo, um video game. Existem emuladores famosos como o [Project64](https://www.pj64-emu.com/) e o [ZSNES](https://www.zsnes.com/), usados para emular os video games Nintendo 64 e Super Nintendo respectivamente.

Além de emular, um emulador pode ir adiante, providenciando uma maior performance, maior qualidade de vídeo e melhor gerenciamento de recursos como CPU e memória.

## Por que aprender sobre emuladores?

Boa pergunta! Entender o básico de emuladores vai te proporcionar uma boa ideia de como computadores funcionam. 

Emuladores são compostos de vários componentes de um computador, como uma memória, uma CPU, um teclado e um display. Cada um desses componentes tem suas características.  O desenvolvimento de um emulador vai te ajudar a entender como um programa é carregado na memória (ROM, *read-only memory*), ou como as instruções do programa são interpretadas e executadas e como as informações são mostradas na tela.

Alguns requisitos são necessários para entender e construir um, como:
* Conhecer alguma linguagem de programação;
* Operações lógicas;
* Deslocamento de bits (bit shifting).

Se você conhece uma linguagem de programação mas não entende sobre operações lógicas e operações de bit, não se preocupe, eu vou colocar referências de estudo e explicar as partes do código que falam sobre isso em detalhes.

O *hello world!* dos emuladores é o **chip-8**, e é ele que vamos construir.

## Um pouco de base

Antes de começarmos a falar sobre o emulador que vamos construir, vamos relembrar ou aprender um pouco sobre dois assuntos que precisamos compreender para seguir este guia.

### Binário e hexadecimal

Nosso sistema número mais comum é o sistema decimal. Isso quer dizer que temos do 0 ao 9 para formarmos outros números, como o 2, o 321, o 9825, etc. Depois do 9, para adicionarmos um número passamos o 9 para 0 e colocamos 1 à esquerda e teremos o 10. Computadores usam outros sistemas de números: binário e hexadecimal.

O [sistema binário](https://pt.wikipedia.org/wiki/Sistema_de_numera%C3%A7%C3%A3o_bin%C3%A1rio) é constituído de 2 números, o 0 e o 1. `Zero é 0`, `um é 1` e o 2 troca o 1 pra 0 e adiciona um 1 à esquerda. Contando em binário: 0, 1, 10, 11, 100, 101, 110, 111, 1000 (0, 1, 2, 3, 4, 5, 6, 7, 8).

O [sistema hexadecimal](https://pt.wikipedia.org/wiki/Sistema_de_numera%C3%A7%C3%A3o_hexadecimal) conta com 16 símbolos. Isso provê uma maior compactação na representação de números maiores. Por exemplo o 15 é representado pela letra `F`. Quanto maior o número, mais isso fica perceptível.

Todas as instruções do chip-8 são analisadas de forma hexadecimal. A instrução de limpar a tela começa com `0x0` e a operação de desenhar é `0xD`.

### Lógica binária

[Lógica binária](https://pt.wikipedia.org/wiki/L%C3%B3gica_bin%C3%A1ria) opera em bits. Usamos para manipular valores e fazer comparações.

Algumas dessas operações você já pode conhecer, mas uma em especial é importante conhecer, o `XOR`. O XOR devolve 1 bit sempre que a quantidade de números 1 for ímpar:

```
 |  V1 |  V2 |  R  |
 +-----+-----+-----+
 |  0  |  0  |  0  |
 +-----+-----+-----+
 |  1  |  0  |  1  |
 +-----+-----+-----+
 |  0  |  1  |  1  |
 +-----+-----+-----+
 |  1  |  1  |  0  |
 +-----+-----+-----+
```

Você pode testar isso com alguma linguagem com [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) ou usando `bash`:

```bash
echo $((0 ^ 0))
0
echo $((0 ^ 1))
1
echo $((1 ^ 1))
0
```

### Deslocamento de bits

Uma das razões do deslocamento de bits existir é a possibilidade de codificar informações importantes usando menos dados. O receptor da mensagem pode decodificar a mensagem, extraindo valores com significado.

As instruções do chip-8, por exemplo, são codificadas de 2 em 2 bytes, e cada byte pode ser dividido em 2 [nibbles](https://pt.wikipedia.org/wiki/Nibble) usando deslocamento de bits. Veremos isso em mais detalhes no próximo post, quando vamos aprender a decodificar as instruções da ROM.

Geralmente a primeira instrução de uma ROM é limpar a tela, a `00E0`. Em binário isso seria `00000000` (00) e `11100000` (E0).

A tela é limpa quando o `OpCode` é `0` e o quarto `nibble` é `0`. Vamos testar usando o REPL do Python:

```python
>>> (0b00000000 >> 4) & 0xF // OpCode
0
>>> 0b11100000 & 0xF // quarto nibble chamado de N
0
```

A instrução de `jump` (`0x1NNN`) faz o código pular até a instrução de memória `NNN`.

```
0b11010 | 0b100101
1A      | 25

JUMP | 0xA25
0x1  | A25
```

`0x1` é a operação de pular e `A25` forma o `NNN`, o quarto, terceiro e segundo nibble dos dois bytes da instrução.

Veremos esse assunto em detalhes na parte 2.

## O que é o chip-8?

O chip-8 é uma linguagem interpretada e, também, uma máquina virtual criada por Joe Weisbecker em 1977 para rodar no [COSMAC VIP](https://en.wikipedia.org/wiki/COSMAC_VIP)

![foto de um computador COSMAC VIP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fa7ev7goqrdkx3tkif1o.jpeg)

A ideia do Joe era rodar pequenos programas e jogos com a ajuda de um teclado de hexadecimal. Em vez de usar linguagem de máquina, o teclado hexadecimal era usado para digitar instruções que seriam interpretadas.

*Interpretadas*? O correto não seria *emuladas*? Não. Nesse guia não vamos construir um emulador exatamente, mas sim um **interpretador de instruções chip-8**. Nosso interpretador vai ler instruções de uma ROM e executá-las uma a uma, em um loop de **ler, decodificar e executar** as instruções carregadas.

Mas qual a diferença entre **interpretador** e **emulador** nesse caso? O chip-8 é um programa que rodava em um computador. Um emulador imita hardware. Simular o chip-8 significa que vamos escrever uma máquina virtual que interpreta comandos via uma linguagem hexadecimal, porém, trazendo vários conceitos vistos em emulação e arquitetura de computadores como PC (program counter), stack, timers, RAM, ROM, etc.

O chip-8 é formado por diversos componentes. Abaixo vamos falar de cada um dos componentes de maneira breve.

### Memória

O espaço de memória deve ser de 4kB (4096 bytes). Toda essa memória é volátil (RAM) e pode ser modificável.

Uma ROM deve começar começar a ser carregada a partir do endereço `0x200` (512 em decimal). Os endereços `0x000` até o `0x1FF` são reservados para o chip-8, porém vamos construir ele usando nosso computador, então não precisamos nos preocupar com isso.

### Registradores

Existem dois registradores: **dados** e de **endereço**.
Eles são usados para gerenciar dados no chip-8. Há várias operações como adição, subtração, leitura de valores, etc. Essas operações se tornam possíveis quando você tem registradores.

O registrador de dados é um array com 16 posições de valor inteiro de 8 bits sem sinal (`u8`). O endereço `0xF` é normalmente usado para configurar uma *flag* (`0` ou `1`), que pode ser usado para indicar se houve colisão ao desenhar no display.

O registrador de endereço não é um array, mas sim uma espécie de ponteiro. Ele é chamado de `index` e é usado para ler e escrever na memória. O registrador de endereços, chamado de `I`, também é usado para desenhar as fontes no display, que serão configuradas através de uma das instruções contidas na ROM. 

### Display

Com as dimensões de **64 pixels** de largura e **32 pixels** (64x32) de altura, o display é usado para renderizar na tela toda atualização identificada pela instrução de `draw` (`DXYN`) que veremos adiante.

No chip-8, os pixels são valores booleanos, `0` ou `1`, `on` ou `off`, `0x0` ou `0x1`. Os displays eram monocromáticos (preto e branco), porém, quando chegar a hora você vai poder usar diferentes cores para os pixels. O display começa com todos os pixels off.

Em desenvolvimento você pode, em vez de escrever na tela usando alguma *engine* de gráficos, escrever no `STDOUT` para conferir se o programa está desenhado da maneira correta.

### Teclado

O teclado do COSMAC VIP tem 16 teclas, por isso é chamado de *hex keypad*. Não vamos construir o teclado, mas precisamos saber que ele existe e que algumas instruções do chip-8 esperam por uma tecla a ser pressionada.

![teclado com 16 teclas do COSMAC VIP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjeg25nzyfn1nlowc6zc.png)

Ao construir seu chip-8 você não precisa seguir esse layout. O layout que usei é como mostrado abaixo:

```
╔═══╦═══╦═══╦═══╗
║ 1 ║ 2 ║ 3 ║ 4 ║
╠═══╬═══╬═══╬═══╣
║ Q ║ W ║ E ║ R ║
╠═══╬═══╬═══╬═══╣
║ A ║ S ║ D ║ F ║
╠═══╬═══╬═══╬═══╣
║ Z ║ X ║ C ║ V ║
╚═══╩═══╩═══╩═══╝
```

Se o seu teclado não é no layout `QWERTY`, tudo bem, você pode mapear outras teclas ou até deixar isso configurável.

### Stack

A stack é uma área de memória relacionada a sub-rotinas que podem ser chamadas durante a execução da ROM.
Como o próprio nome já diz, isso é uma stack. Se a sua linguagem de programação suporta o uso de stacks, use-a para maior legibilidade.

Os valores guardados nessa stack são de 16 bits.

Em Rust, os vectors possuem as funções `push` e `pop`:

```rust
let mut stack: Vec<u16> = Vec::new();
stack.push(0xF);
stack.pop();
```

Existe uma limitação no chip-8 de 12 a 16 endereços de memória, porém, não precisamos nos preocupar com isso, deixando o tamanho da stack ilimitada.

### Fontes

O chip-8 vem com fontes pré-determinadas. Isso significa que existe na memória do chip-8 sprites que são basicamente números e letras no intervalo hexadecimal: `0` a `F`. Cada uma dessas representações tem 4 pixels de largura e 5 pixels de altura.

Guarde essas fontes em uma área de memória antes do `0x200` que falamos acima. Por algum motivo ficou popular gravar a partir do `0x50`. É possível definir e carregar as fontes usando um código similar ao debaixo:

```rust
static FONTS: [u8; 80] = [
    0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
    0x20, 0x60, 0x20, 0x20, 0x70, // 1
    0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
    0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
    0x90, 0x90, 0xF0, 0x10, 0x10, // 4
    0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
    0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
    0xF0, 0x10, 0x20, 0x40, 0x40, // 7
    0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
    0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
    0xF0, 0x90, 0xF0, 0x90, 0x90, // A
    0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
    0xF0, 0x80, 0x80, 0x80, 0xF0, // C
    0xE0, 0x90, 0x90, 0x90, 0xE0, // D
    0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
    0xF0, 0x80, 0xF0, 0x80, 0x80, // F
];

for (n, font) in FONTS.iter().enumerate() {
    machine.memory[0x50 + n] = *font;
}
```

Existe uma instrução (`FX29`) que guarda o endereço da fonte a ser desenhada. Então, a instrução de desenhar vai usar esse valor de memória para escrever a fonte na tela.

### Timers

O chip-8 conta com 2 timers (temporizadores): **delay timer** e **sound timer**.

Ambos timers são decrementados numa taxa de `60khz` até chegar a zero. Ao chegar em zero, cada um terá seu evento interrompido e a contagem recomeça.

O delay timer é usado para sincronizar eventos.
O sound timer vai tocar um som (*beep*) enquanto o valor for maior que zero.

Os dois timers podem ser inicializados como um inteiro de 8 bits sem sinal (`u8`).

```rust
if self.delay_timer > 0 {
    self.delay_timer -= 1;
}

if self.sound_timer > 0 {
    self.sound_timer -= 1;
}
```

---

No próximo post vamos abordar, em detalhes, a codificação do chip-8 usando a linguagem Rust, entendendo como decodificar as instruções e executar as operações.