10 noviembre, 2013

El esquema básico de funcionamiento de un emulador

La función principal del emulador será simular la arquitectura hardware del ordenador que está emulando. Vamos a centrarnos de momento en el procesador. El funcionamiento básico del procesador es procesar una instrucción de código máquina, luego otra, luego otra... así que cabe pensar que la parte principal del emulador no va a ser otra cosa que un bucle.

Durante ese procesamiento de una instrucción de código máquina ocurren varias cosas:
  1. El procesador obtiene la siguiente instrucción de la memoria.
  2. Se incrementa el registro CP.
  3. En función de la instrucción se hace una cosa u otra.
  4. (Normalmente) se comprueba si hay que hacer otras cosas: comprobar si se ha disparado una interrupción, si se ha pulsado una tecla, si se ha movido el ratón o simplemente refrescar la pantalla.
Se muestran los pasos anteriores en forma de diagrama donde cada paso es un círculo que conecta con otro.

Es posible implementar el paso 3 usando un SWITCH gigante que dependa del opcode que se ha leído en el paso 1 y que en función del valor del opcode haga una cosa u otra. Por ejemplo, supongamos que tenemos un conjunto de instrucciones bastante crudo basado en tres opcodes: 0x00 = LD A, [inmediato], 0x01 = ADD A, inmediato, 0x02 STO [inmediato]. Los corchetes indican que lo que se lee no es el inmediato, sino el valor que haya en la posición de memoria indicada por el inmediato. Un esquema básico y no del todo correcto (ya contaré) del bucle principal sería algo como lo siguiente:

for(;;) {
    opcode = mem[pc++] // pasos 1 y 2
    switch(opcode) {
        case 0x00:
            a = mem[ mem[pc++] ];
            break;
        case 0x01:
            a += mem[pc++];
            break;
        case 0x02:
            mem[ mem[pc++] ] = a;
            break;
        default:
            printf("Opcode no reconocido.");
            // tratar error o detener emulador
            break;
    }
    
    // añadir paso 4 aquí
}

He dicho que no es del todo correcto por que no estamos teniendo en cuenta la suma de los ciclos de reloj o los T-States, algo que como mínimo es importante para poder ejecutar correctamente el paso 4. Sobre el funcionamiento del paso 4 me tengo que explicar bastante por lo que prefiero dejarlo para un futuro post separado.

A modo de cierre de este post, decir que en procesadores más complejos, el SWITCH puede complicarse bastante. Puede haber cientos de instrucciones distintas y esto puede hacer que haya dos formas distintas de actuar. Una es tirarse directamente a hacer un CASE para cada valor que pueda haber, y otra es pararse a estudiar un poco el conjunto de instrucciones con el que se está trabajando.

La segunda parte puede ofrecer mayores ventajas, especialmente en términos de complejidad del código. La arquitectura del Z80 tiene muchas instrucciones que hacen la misma operación pero con distintos operandos. Por ejemplo, hay una instrucción LD A, B, otra LD A, C, otra LD A, D... y así sucesivamente. En este caso al estudiar el conjunto de instrucciones podría verse que todas estas instrucciones tienen el mismo opcode, sólo que cambian dos o tres bits que son los que indican cuál es el segundo operando, mientras que el resto de bits siguen sin cambiar, y eso podría hacer que se simplificara el procesamiento al no repetir casos, comprobando únicamente si la instrucción es de tipo LD A, x viendo si tiene los bits comunes puestos en 1 y luego determinando cuál es el segundo operando viendo esos bits que sí cambian.

En este sentido, estas tablas que hay en la web Z80Info son bastante útiles ya que hacen esto mismo para todas las instrucciones del procesador Z80. El código se hace más complejo porque tienes varios niveles de profundidad en el código (se hace un SWITCH para unos bits, dentro otro switch para otros bits y dentro otro SWITCH para otros bits), pero se gana en que no hay que implementar opcode a opcode el conjunto de instrucciones, sino que se agrupan aquellas instrucciones comunes que cambian sólo en los datos de entrada.

No hay comentarios:

Publicar un comentario