En el sentido más puro, un registro no es más que un conjunto de flip-flops. La diferencia entre un flip-flop y un registro es que mientras que el primero está diseñado para guardar un bit, un registro está diseñado para guardar varios bits. Sin embargo, el procesador por lo general trabajará de forma conjunta con todos los bits del registro mediante el mismo identificador.
Los procesadores tienen varios tipos de registros. Algunos registros son específicos para algunas tareas y son usados por el propio procesador, como el registro de instrucción, donde se guarda el código de operación que el procesador esté ejecutando en un momento; el contador de programa, que guarda la dirección de memoria de la instrucción que se está ejecutando, o una batería de registros que pueden ser usados por el programa por lo que se los denomina registros de propósito general.
Orientado a la línea de este blog, el procesador Z80 dispone de varios registros, siendo estos los más principales:
- Dos bancos de registros de propósito general. Mientras que la mayoría de procesadores tienen un único banco, el Z80 tiene dos. El programa sólo tiene acceso al banco principal, pero por medio de algunas instrucciones, puede intercambiar los bancos para acceder al otro. El registro más importante es el A, que es el acumulador, y el F, que guarda los flags. Otros registros son el B, C, D, E, H y L. Los registros secundarios son A', F', B', C', D', E', H' y L'.
- Unos registros de índice (IX, IY)
- Un contador de programa (PC) y un puntero de pila (SP).
- Un registro de interrupción (I) y un registro de refresco (R)
- El registro de instrucción.
Cuando se construye un emulador, estos registros se implementan en memoria usando unas variables, ya que por lo general no tenemos acceso a los registros del procesador físico de la máquina anfitrión. Esto puede ser un problema al emular máquinas más potentes ya que acceder a la memoria es más costoso que acceder a un registro por lo que nuestro emulador se sobrecargará más. En mi caso, que estoy emulando un Z80, ni me va ni me viene, ya que se trata de una máquina que tiene apenas unos pocos megahercios.
Otra de las cuestiones es cómo implementar tantas variables en el programa. Es importante tener en mente que van a ser tocadas por todo el emulador, por lo que deben ser bien accesibles.
- Una forma es juntando todas las variables en una o varias estructuras. Mínimo una estructura con todas las variables de registro, aunque puede haber una estructura con los registros de propósito general dentro de otra estructura mayor con más variables. El acceso a estas variables es por medio de registro base: la CPU anfitrión calculará la posición de la variable del registro a partir de la dirección de memoria de la estructura y un incremento en el registro. Esto es algo más costoso que acceder directamente a la posición de memoria, que podría hacerse en un paso.
- Otra forma es manteniendo cada variable separada. El mantenimiento en este caso es más costoso. Aquí hay que tener en cuenta el ámbito, especialmente si vamos a usar funciones en el emulador, ya que hay que permitir a las funciones trabajar con unos registros u otros. Otra forma sería usar variables globales; en este caso el mantenimiento no se vuelve tan asqueroso como puede parecer puesto que en el fondo son variables con un objetivo claro y que son usadas por todo el programa así que podría merecer hasta la pena saltarse los principios de la programación por un rato.
Sobre las variables, otra pregunta es con qué tipo de datos se debe trabajar. Puesto que de momento el emulador lo estoy haciendo en Blitz3D, sólo tengo acceso a un único tipo de datos, Integer, que de acuerdo con la documentación tiene 32 bits. Sin embargo, al trabajar en C, hay que tener en cuenta que hay múltiples tipos de datos según su tamaño. Hay dos opciones, cada una tiene sus ventajas y sus desventajas:
- Usar tipos int para todo.
Pro: usas el tamaño de palabra nativo de la CPU anfitrión. Esto suele tener un mejor rendimiento por la forma en la que funcionan los procesadores.
Contra: debes tener en cuenta que si los registros que emulas tienen menos bits que tu tipo de datos deberás hacer ajustes (usar máscaras AND o cualquier otra técnica) para que la variable no tenga más bits de los que se supone que debe tener. - Usar los tipos del mismo tamaño que los registros de la plataforma, preferentemente unsigned para que no tengas problemas con el signo ya que al fin y al cabo, del signo de los "registros" se encarga la CPU que estés emulando.
Pro: no es necesario tragar con el problema de variables más grandes que los registros que se esté emulando.
Contra: justo el inverso del caso anterior: es posible que algunas CPUs tengan menos rendimiento al trabajar con tipos de datos de menor tamaño que la arquitectura del procesador.