Introduccion

Lo primero de todo. Si has llegado hasta aquí sin saber cómo, esta es la documentación/anotaciones de mi proyecto público en Github para emular la Gameboy en Rust.

Creo que una buena forma de comprender cómo es el funcionamiento de un ordenador y sus componentes desde un punto de vista de software/coding es tratando de emular sus comportamientos y relaciones. Es por ello que he elegido una de las consolas portátiles más míticas como lo fue la Gameboy de Nintendo. La idea es replicar el comportamiento al 100% de esta consola al mismo tiempo que entender cómo funciona un sistema de este tipo.

Elijo la Gameboy porque ya hay mucha documentación y otros proyectos que han cumplido con este mismo objetivo en los que me puedo apoyar y tratar de no alargar el proceso demasiado tiempo.

¿Por qué en Rust?

Es normal que en los proyectos en los que se trata de emular sistemas se utilicen lenguajes considerados low-level dado que el rendimiento es un factor clave. Sin embargo, es cierto que lo poco exigente que resulta la Gameboy para los ordenadores actuales hace que podamos resolver este problema en casi cualquier lenguaje.

Entonces, ¿por qué Rust? Era un lenguaje de bajo nivel con el que quería probar hacer algo que resultara retador. Además de que lo aprendido en este proyecto podría ser de utilidad para futuros emuladores que quiera hacer donde si deba tener más en cuenta el rendimiento. También es un lenguaje con una comunidad sólida en la que poder apoyarme en el camino.

CPU

La Gameboy lleva un procesador Sharp LR35902, se trata de un híbrido entre el Zilog Z80 y el Intel 8080. Este corre a 4.19MHz, o lo que es lo mismo, puede hacer 4.19 millones de ciclos de reloj por segundo.

El Intel 8080 fue un procesador usado por diferentes ordenadores en la época de los 70s 80s, incluyendo el ordenador personal Altair 8800.

No entraremos en los detalles específicos que diferencian al LR35902 de los otros dos procesadores ya mencionados porque no nos será necesario tener en cuenta para la emulación.

Registros

La CPU esta compuesta por 8 registros diferentes, estos se encargan de almancenar pequeñas cantidades de información que se utilizan para realizar operaciones. Como la CPU de la Gameboy es de 8-bits, esto quiere decir que cada registro puede almacenar un valor de 0 a 255 (2^8 - 1). O lo que es lo mismo, 8 bits (a.k.a 1 byte).

16-bitHiLoName/Function
AFA-Accumulator & Flags
BCBCBC
DEDEDE
HLHLHL

Aunque la CPU solo tiene registros de 8-bits, hay instrucciones que permiten acceder a los registros de 16-bits. Por ejemplo, la instrucción LD HL, 0x1234 carga el valor 0x1234 en el registro HL. Para ello, se utiliza el registro H para almacenar el byte más significativo y el registro L para almacenar el byte menos significativo. (No importa si esto no lo entiendes por ahora, lo veremos más adelante.)

En el código de la CPU, los registros se representan como u8 (unsigned 8-bit integer)

#![allow(unused)]
fn main() {
pub struct Cpu {
    pub a: u8,
    pub b: u8,
    pub c: u8,
    pub d: u8,
    pub e: u8,
    pub h: u8,
    pub l: u8,
    pub f: u8,
    ...
}
}

Referencia al código

Regsitro F (Flags register)

El registro F es un registro especial que se utiliza para almacenar información sobre el resultado de las operaciones realizadas por la CPU. Los 4 bits menos significativos siempre serán 0. Los 4 bits más significativos se utilizan para almacenar cuando ciertas cosas han ocurrido. Por el momento no voy a entrar en más detalles sobre esto, pero lo veremos.

BitNameExplanation
7zZero flag
6nSubtraction flag (BCD)
5hHalf Carry flag (BCD)
4cCarry flag

Y aquí un diagrama de los bits del registro F:

   ┌-> Carry
 ┌-+> Subtraction
 | |
1111 0000
| |
└-+> Zero
  └-> Half Carry