-[ 0x04 ]--------------------------------------------------------------------
-[ Emulador de Siemens S45 ]-------------------------------------------------
-[ by FCA00000 ]-----------------------------------------------------SET-31--

Emulador de Siemens S45

*****************
PROPOSITO
*****************
En una entrega anterior expliqu que el telfono mvil Siemens S45 tiene
un procesador C166 y es posible modificar el programa de la Flash para
cambiar el funcionamiento de su Sistema Operativo.

Tras analizar muchas de sus rutinas, modificar algunas, y extender otras, he
llegado a la conclusin de que me sera til hacer una especie de emulador.
Esto me permitira verificar mis programas antes de probarlos en el mvil, ya
que el proceso escribir-compilar-transferir-probar-arreglar no es muy eficiente,
sobre todo porque muchas veces el programa est mal escrito y resetea el mvil.

Este artculo es el resultado de esa investigacin.
Es muy posible que t, lector, no tengas el mismo inters que yo.
Esto es como el cine: si no te interesa la pelcula, te levantas y te vas.
Total, para lo que has pagado...
De todas maneras me gustara animarte a continuar la lectura, incluso si
en algn momento te pierdes entre tanto detalle.
A lo mejor en un futuro quieres hacer algo parecido, sin caer en mis
mismos errores.

Seguramente he cometido muchos fallos de diseo. Algunos los descubr a
tiempo y no supusieron grandes cambios. Otros estn todavia ocultos y
quizs nunca los descubrir.
Por otro lado estn los fallos de concepto. Seguro que he dado algunas cosas
por seguras, y no siempre es as. Esto es lo ms difcil de arreglar, ya que
una vez que una idea se me mete en la cabeza, cuesta mucho sacarla de all.

Durante el proceso decid que era bueno aprovechar ideas de otros. Para ello
consegu y estudi el cdigo fuente de algunos otros emuladores, con la
esperanza de aprender buenas tcnicas. Entre ellos:
XZX     - emulador de ZX-Spectrum para UNIX. Muy til, ya que conozco bastante
          bien el sistema del Z80 y el Spectrum.
coPilot - emulador de Palm para Windows. Me sirvi porque la Palm tiene un
          sistema de 32 bits y muchos puertos.
Wine    - emulador de Windows para Linux. Aqu aprend la metodologa para
          averiguar los parmetros de entrada/salida
SMTK    - emulador de Java para telfonos Siemens. Pens que podra hacer
          funcionar mis rutinas en un emulador hecho por el propio fabricante.
          Lamentablemente al no disponer del cdigo fuente, me restringe 
          bastante.

Todos estos emuladores estn escritos en lenguaje C. Es el ms adecuado
debido a su velocidad y portabilidad. Adems muchas de las operaciones
del C166 tienen que ver con bits, operaciones OR, manejo de punteros, y
conversiones de datos, todas ellas fcilmente implementables en C.
Adems, es mi lenguaje de programacin habitual.

Nota: usar siempre nmeros en hexadecimal. En general los antepondr
de '0x' pero a veces lo que hago es comenzarlos por '0' y finalizarlos por 'h'.

*****************
Microprocesador
*****************
Como ya tena el manual del C166, el primer paso era estudialo completamente
para entender todas las instrucciones. y saber la tarea a la que me enfrento.
Existen varios manuales: algunos explican el funcionamiento de las
instrucciones y otros que explican el procesador.

Por supuesto que un mvil es mucho mas que un simple procesador pero yo
no pretendo hacer una emulacin completa.

El C166 es un microprocesador de 16 bits, as que todas las instrucciones
ocupan 2 bytes, lo cual se conoce como palabra (word).
Esto hace un total de 65.536 pero tranquilo, que en realidad slo el
primer byte decide el comportamiento. El segundo byte suele decir
los parmetros o variables que usa dicha instrucccin.
Es ms: algunas instrucciones ni siquiera se usan.
Como el formato es little-indian, el byte menos significativo est en
la posicin menor de memoria. Esto es muy importante.

Las instrucciones estn almacenadas en la memoria. Aunque no sea del todo
cierto, por ahora te sirve si digo que ocupa 16 Mg, separadas entre RAM y Flash.
La RAM contiene datos que se pueden leer y escribir.
La Flash suele almacenar los programas y rutinas, y obviamente se puede leer.
Algunas zonas se pueden escribir usando rutinas especficas.

Entre estas zonas escribibles estan:
-FlexMem, para almacenar programas en java/Html , ficheros de texto,
 imgenes, sonidos, y en general cualquier fichero que desees transferir
 desde un PC por infrarrojos, o desde otro mvil. Ocupa 370 Kb, no mucho.
-EEPROM, para almacenar datos de configuracin ms o menos permanentes, pero
 que cambian a veces: servidor de SMS, puntuacin en los juegos, notas,
 bookmarks de sitios WAP, citas, alarmas, ...

Hay otras zonas de la Flash que no se pueden escribir:
-zonas de caracteres, msicas, mens, datos en general
-rutinas del mvil
Bueno, tericamente no se pueden escribir, pero si adquieres un cable especial
hay muchos programas que permiten escribir la memoria: el mejor es v_klay.

Esto es indispensable para mis propsitos: necesito escribir programas y
meterlos en la Flash, para entender cmo funciona.

La memoria se accede mediante dos modos:
-en 256 bloques de 64Kb, llamados segmentos. Se usa la notacin 0x123456
-en 1024 pginas de 16Kb. Se usa notacin 0048:3456
Para convertir de una pgina a un segmento, multiplica por 0x4000
As, la pgina 03F2h es lo mismo que el segmento 0xFC80000
Esto ya lo expliqu en el articulo de SET30 sobre parcheo de la Flash y
de todas maneras quedar claro a lo largo del artculo.

El funcionamiento de un emulador es simple: lee una instruccin, la
procesa, y salta a la siguiente. As que en este punto puedes dejar
de leer este artculo y pasar a otra cosa :-)

Lo primero que necesito es una variable que me diga cul es la direccin
de la instruccin que debo procesar.
Normalmente en el microprocesador real hay un registro con este propsito.
En el C166 se llama IP: Instruction pointer.
Es un registro de 16 bits (como todos), as que slo vale entre 0 y 0xFFFF.
?Como se hace entonces para ejecutar cdigo ms all de 0xFFFF ?
La solucin es otro registro CSP=Code Segment Pointer que apunta al
segmento, por lo que vale entre 0 y 0x03FF.
Por tanto, puedo saber dnde estoy sin ms que tomar CSP y IP:
posicion=CSP*0x10000 + IP;
Y aqu est mi primer error: La lnea anterior es el mismo resultando que
haciendo CSP<<0x10 + IP, que tambin es lo mismo que CSP<<0x10 | IP , y sta
es la forma de ejecucin ms rpida.
Luego pens que era mejor dejar estas decisiones al compilador. Al fin y al
cabo, la tecnologa de compiladores ha evolucionado enormemente, y son
capaces de decidir las mejores optimizaciones para el microprocesador
en el que se ejecutar el programa.
De todas maneras nunca est de ms hacer unas mnimas comprobaciones.
El autor debera saber donde estn los puntos crticos de su programa, ?no?

*****************
CARGA INICIAL
*****************
El siguiente paso es cargar en memoria el equivalente a la memoria del
sistema emulado. En el emulador del Spectrum esto es fcil, ya que se
puede guardar la ROM en un fichero y transferirlo al PC.
Los ms viejos del lugar seguro que recuerdan el "transfer" desarrollado
por la revista Microhobby para hacer copias de los programas.

En mi caso har lo mismo: la Flash la saco del mvil, pero ?y la RAM?
Pues lo mismo: hago un programa S45dumper que cargo en el mvil y cuando
quiero, pulso una tecla (o voy a un men) y llamo a mi rutina, que empieza
a volcar datos por el puerto serie, y los recojo en el PC.

alguna_posicion_en_flash:
   cuando_tecla_magica GO TO S45dumper
   sigue_donde_estaba

S45dumper:
  desde 0 hasta 0xFFFFFF
    manda S45mem[i]
  manda registros GPR
  manda flags
  manda CSP y IP
fin S45dumper

Los registros GPR se llaman R0 .. R15 y son de 16 bits.
Se usan como almacenamiento temporal de datos.

Dependiendo del momento en que haga este volcado, los datos de la RAM sern
distintos. Esto tiene de bueno que puedo hacer una foto exacta de la memoria
en el momento que quiera. Por supuesto que S45dumper tiene que guardar los
registros GPR para no modificar nada y permitir seguir la ejecucin tanto
en el sistema real como el emulado.

Bueno, supongamos que he hecho y ejecutado este programa. Ahora tengo
un fichero de 16 Mg con la memoria del S45.
Cargo este fichero en la memoria del emulador en una zona de memoria
llamada S45mem[] que ocupa 0xFFFFFF+1 bytes.

*****************
EL PRIMER PASO
*****************
Ahora viene la siguiente cuestin: ?Dnde empiezo?
Normalmente los procesadores empiezan en la direccin 0x000000 , pero como
yo he hecho una copia de la memoria, las rutinas de inicializacin ya se han
ejecutado, pero s que la copia la ha hecho mi propio programa, as que la
siguiente instruccin a ejecutar es la ltima de S45dumper

Ahora ya s por dnde debo seguir: sigue_donde_estaba.
Suponer que vale 0xFCA000

Leo el dato en S45mem[0xFCA000].
Suponer que vale 0xCC
Leo el dato en S45mem[0xFCA000+1].
Suponer que vale 0x00

Entonces yo s que la instruccin 0xCC00 significa NOP , o sea, no
hacer nada y seguir con la siguiente instruccin:

Leo el dato en S45mem[0xFCA000+2].
Suponer que vale 0x97
Leo el dato en S45mem[0xFCA000+3].
Suponer que vale 0x97

La instruccin 0x9797 significa PWRDN , o sea, power-down, con lo que el
mvil se apagar. En el emulador lo que har es salir del programa.

*****************
SIGUIENTES PASOS
*****************
Pero est claro que debo tener una rutina que compruebe cada uno de los
cdigos, y haga lo que tenga que hacer:

si COMANDO es 0xCC00 entonces 
   {
   salta a siguiente instruccin
   }
si COMANDO es 0x9797 entonces 
   {
   sal del programa
   }
....... y as con todos los comandos

Hay varias maneras de hacer eso ms eficiente: la primera es con la instruccin
"switch", que saltar directamente al bloque de ese comando.
Eso es en teora, ya que investigando el cdigo generado por el compilador he
visto que en realidad hace todas las comparaciones una por una.
O sea, que es igual de ineficiente.

Otra posibilidad es usar una tabla de funciones. El lenguaje C permite
definir funciones rutinaCC00() , rutina9797() , ...
y una tabla de punteros a funciones
void (*tabla_rutina[])() = { rutinaCC00, rutina9797 };

que se llaman con:
tabla_rutina[COMANDO]();

Esto es muy rpido: el acceso es directo al trozo de programa que emula
dicha instruccin. Es mejor usar nicamente el primer byte de la
instruccin, y el propio programa debe verificar y interpretar el segundo byte.

Hay una tercera posibilidad.
Ya que slo el primer cdigo del comando define la instruccin, puedo usar
un mini algoritmo de bsqueda dicotmica basado en 8 bits:
COMANDO8 = COMANDO>>8;
if COMANDO8<0x80 then
  {
  if COMANDO8<0x40 then
    {
    if COMANDO8<0x20 then
      {
      if COMANDO8<0x10 then
       {
       if COMANDO8<0x08 then
        ......
         llama rutina correspondiente
       }
      }
    }
  }
else
  {
  if COMANDO8<0xC0 then
    {
    if COMANDO8<0xB0 then
    }
  }

Con lo cual se reduce a 8 comparaciones, sea cual sea la rama que se toma.
Esto es mucho ms eficiente que 256 (mximo) comparaciones.

Tambin se puede hacer un estudio de las rutinas ms comunes y ponerlas al
principio para que la bsqueda las encuentre lo ms pronto posible.
Obviamente el comando NOP y PWRDN estaran al final de la lista, ya que
casi nunca se usan.
Esto supone un estudio inicial y un ajuste a posteriori, pero es la
tcnica que mejor funciona para mi caso, ya que la instruccin MOV supone
el 50% de las instrucciones, y  CALLS, MOVB, ADD y SUB suponen otro 45% .
De todos modos esto es la primera optimizacin: necesitar todas las que
pueda imaginar y sea capaz de desarrollar.

************************
INSTRUCCIONES EN DETALLE
************************

Ya que MOV es la instruccin ms comn, voy a analizarla.
Existen varias variaciones de MOV, con cdigos
0x88, 0x98, 0xA8, 0xB8, 0xC8, 0xD8, 0xE8,
0x84, 0x94, 0xC4, 0xD4,
0xE0, 0xF0,
0xF2,
0xE6, 0xF6,

En general, MOV vale para copiar un valor en otro.
El fuente y el origen puede ser
-un valor inmediato, por ejemplo 0x1234
-un valor indirecto, por ejemplo: el valor de la memoria 0x5678
-un registro GPR, por ejemplo R3 o R15
-un registro SFR

As, el comando 0xF0 sirve para copiar un registro GPR en otro.
?Cmo se sabe cuales son los registros involucrados? Mirando el siguiente
dato en S45mem[i+1]. Llamar a este dato 'c1'.
Se parte el byte c1 en dos trozos de 4 bits , resultando la parte
alta c1H y la baja c1L , ambos con un valor entre 0 y 0xF
El valor c1H indica el registro destino, y c1L es el fuente.
Por ejemplo, si
S45mem[i]=0xF0 y S45mem[i]=0x45 entonces
c1=0x45
c1H=4
c1L=5
registro destino: R4
registro fuente: R5
con lo que la instruccin es
mov R4, R5
Si R4 vale 0x4444 y R5 vale 0x5555, tras esta instruccin, resulta
R4=0x5555 y R5=0x5555 (no cambia)

Lo que sigue ahora es bastante especfico del C166, as que puede extraar
a aquellos que slo conozcan el 80x86 o Z80.
Hay 2 tipos de variables en el C166: los registros SFR y los GPR .
A partir de la memoria 0x00F000 hasta 0x00FFFF se guardan 0x1000 (4096 en
decimal) bytes que almacenan 0x800 (2048 en decimal) variables globales
de 2 bytes (1 word) que se pueden usar en cualquier rutina.
Por ejemplo, 0x00FE14 se llama STKOV y contiene el valor mximo de la pila.
Si en la pila hay ms valores que el nmero guardado en STKOV , se
produce un error de desbordamiento superior de pila.
Otro de los valores es 0x00FE0C llamado MDH que contiene la parte entera del
resultado de la ltima divisin.
Otra variable SFR es 0x00FE42 llamado T3 que contiene el valor del
puerto T3, que resulta estar conectado al teclado.
Cuando leo este valor, en realidad obtengo la tecla que est pulsada.
Hasta aqu, nada espectacular.
As, hasta 0x800 variables. No todas tienen un nombre, y no todas se usan.

Hay otro 16 registros GPR (R0 hasta R15) que tambin estn en esta
memoria, accesibles mediante doble indireccin.
Primero hay que leer el SFR llamado CP=Context Pointer en 0x00FE10. A este
valor resultante hay que sumarle el ndice del registro GPR, multiplicado por 2.
Este ndice es 0 para R0 , 2 para R1, 4 para R2, 6 para R3, 8 para R4, ...
Por ejemplo, para leer R5 se hace:
tomar CP = S45mem[0x00FE10]+S45mem[0x00FE10+1]*0x100    (formato little-indian)
Suponer que CP vale 0xFBD6
Entonces R5 se guarda en 0xFBD6+5*2 , y
vale S45mem[0x00FBD6+5*2]+S45mem[0x00FBD6+5*2+1]*0x100
Esto hace que sean equivalentes
mov r4, r5
y
mov r4, [0xFBE0]
A la mayora de los programadores esto les da igual, y usan los registros GRP
sin importarles dnde estn almacenados, pero yo tengo que hacer un emulador
lo ms exacto posible al modelo real.

El usar registros GPR indexados permite una tcnica muy util para multitarea.
Suponer que tengo Tarea1 con sus valores R0, .. R15 y deseo pasar a Tarea2.
Tarea1 guarda un puntero a la memoria 0x001000 , y all guardo los registros
haciendo que CP valga 0x1000.
Similarmente Tarea2 sabe que tiene que guardar sus registros en 0x002000 .
Si hago CP=0x2000, la instruccin
mov r4, #4444h
guardara el valor 0x4444 en la memoria 0x002000+4*2
Puedo conmutar a Tarea1 haciendo otra vez CP=0x1000, y ahora r4 apunta
a 0x001000+4*2, que no tiene el valor 0x4444 
As puedo tener varias copias de los registros. Una conmutacin de tareas
es simplemente cambiar CP ; no necesito guardar los registros R0-R15 antes
de pasar a la nueva tarea.

Lo malo es que mi emulador, para emular una instruccin tan simple como
E6 F4 67 45  = mov r4, #4567h
necesita hacer:
-leer S45mem[IP]
-vale E6, que significa MOV
-leer S45mem[IP+1]
-vale F4, que significa registro R4
-tomar CP=S45mem[0x00FE10]+S45mem[0x00FE10+1]*0x100
-vale 0xFBD6
-sumar 4*2, que da FBDE
-leer S45mem[IP+2], que vale 0x67
-poner el valor 0x67 en S45mem[0x00FBDE]
-leer S45mem[IP+3], que vale 0x45
-poner el valor 0x45 en S45mem[0x00FBDE+1]

Observar que el byte menos significativo 0x67 se escribe en la
memoria inferior S45mem[0x00FBDE].

Una instruccin todava ms compleja es
mov r4, r5
pues implica adems leer R5 antes de meterlo en R4.

Todava peor es
A8 45 = mov r4, [r5]
que significa: lee el valor que est apuntado por r5, y mtelo en r4
-leer S45mem[IP]
-vale A8, que significa MOV , con indireccin
-leer S45mem[IP+1]
-vale 45, que significa:
 -el destino es el registro R4
 -el fuente est apuntado por el registro R5
-tomar CP=S45mem[0x00FE10]+S45mem[0x00FE10+1]*0x100
-vale 0xFBD6
-sumar 5*2, que da FBE0.
-leer S45mem[0x00FBE0], que vale por ejemplo 0x34. Recordarlo
-leer S45mem[0x00FBE0+1], que vale por ejemplo 0xAB. Recordarlo
-no necesito recalcular CP, gracias a Dios
-sumar 4*2 a FBD6, que da FBDE.
-poner el valor 0x34 en S45mem[0x00FBDE]
-poner el valor 0xAB en S45mem[0x00FBDE+1]

?Piensas que esto es lo ms complicado? Todava te queda mucho por ver
D8 16  = mov [r1+], [r6]
O sea:
-lee el valor apuntado por R6
-escrbelo en el valor apuntado por R1
-incrementa R1
Esta instruccin se usa normalmente para mover unos cuantos datos entre
una posicin de memoria y otra. Felizmente esto est centralizado en unas
pocas rutinas, pero de todos modos tengo que implementarlo.

Similar:
C4 61 03 00  = mov [r1+#3h], r6
-lee R6 (directamente, no indirectamente)
-obtener R1
-sumarle 3
-en esta posicin, meter el valor de R6

No, todava no he terminado
Observa la diferencia entre
E6 F1 34 12  = mov r1, #1234h
y
F2 F1 34 12  = mov r1, 1234h
La primera mete en el registro R1 el valor 0x1234h
La segunda mete en el registro R1 el valor que est en la memoria 0x1234h
Es decir, la primera es acceso directo, y la segunda es indexado.

*************************
ACCESO A BYTES
*************************
Los primeros 8 registros GPR desde R0 hasta R7 se pueden tratar como words o
como bytes, con una parte baja RL y otra alta RH ; pero en este segundo caso
hay que usar la instruccin MOVB :
E6 F3 34 12 = mov r3, #1234h
es lo mismo que
E7 F6 34 00 = movb rl3, #34h
E7 F7 12 00 = movb rh3, #12h
donde 
-el primer byte E7 quiere decir MOVB
-el siguiente byte F6 slo usa la parte baja: 6
-el valor 6 se divide entre 2 y se toma la parte entera, dando 3
-como es valor 6 es par, se usa la parte baja RL . En este caso, RL3
-se lee el siguiente byte: 0x34 , y se asigna a RL3
-se desecha el cuarto byte

-en la otra instruccin E7 F7 12 00 = movb rh3, #12h
-el primer byte E7 quiere decir MOVB
-el siguiente byte F7 slo usa la parte baja: 7
-es valor 7 se divide entre 2 y se toma la parte entera, dando 3
-como es valor 7 es impar, se usa la parte alta RH . En este caso, RH3
-se lee el siguiente byte: 0x12 , y se asigna a RH3
-se desecha el cuarto byte
Esto permite trabajar con bytes adems de con words.

Por supuesto que R3 vale RL3+RH3*0x100

*************************
MODOS DE DIRECCIONAMIENTO
*************************
Ahora es cuando las cosas se complican de verdad: DPP
Aviso que esto es difcil. A lo mejor deberas pasar al siguiente apartado.
Pero como dicen los que hacen yoga: si no duele, no lo ests haciendo bien.

La instruccin
F2 F1 34 12  = mov r1, 1234h
usa acceso indexado con valores origen (nunca pasa con registros origen)
Entonces hay 4 SFR que intervienen: DPP0-DPP3
Estos SFR se almacenan en 0x00FE00+i*2 , con 0<=i<=3
Antes de leer la direccin de memoria (0x1234 en este ejemplo) se toman
los 2 bits ms significativos y se usa DPPi , siendo i el valor de estos 2 bits.

Usar dicho DPPi como pgina, y el valor inicial como offset.
A ver si con un ejemplo queda ms claro. Como 
0x1234=0001.0010.0011.0100 , los 2 primeros bits valen 00.
Entonces se usa el registro DPP0 como pgina para el valor 0x1234.
Es decir, que la memoria que no se leer de 0x001234 , sino de
(S45mem[0x00FE00+0*2]+S45mem[0x00FE00+0*2+1]*0x100)*0x4000+0x1234
O sea, que mete en R1 el valor
S45mem[ (S45mem[0x00FE00+0*2]+S45mem[0x00FE00+0*2+1]*0x100)*0x4000+0x1234 ]

Un ejemplo:
F2 F7 34 A2   = mov r7, 0A234h
-leer S45mem[IP]
-vale F2, que significa MOV , con indexacin de valor origen
-leer S45mem[IP+1]
-vale F7, que significa:
 -el destino es el registro R7
 -el fuente est apuntado por el valor 0xA234
-tomar CP=S45mem[0x00FE10]+S45mem[0x00FE10+1]*0x100
-vale 0xFBD6
-sumar 7*2, que da FBE4. Despus lo necesitar
-rotar 0xA234 hacia la derecha 14 veces para quedarse con los 2 primeros bits.
-vale 10 (en bits. O sea, 0x2 en hexadecimal)
-necesito saber DPP2, almacenado en FE00+2*2
-leer S45mem[0x00FE04], que vale por ejemplo 0x39
-leer S45mem[0x00FE04+1], que vale por ejemplo 0x00
-calcular DPP2=S45mem[0x00FE04]+S45mem[0x00FE04+1]*0x100=0x39+0x00*0x100=0x39
-considerar 0x39 como pgina, es decir, multiplicarlo por 0x4000
-obtener 0xE4000
-eliminar los 2 primeros bits del valor 0xA234
-o sea, 0xA234 & 0x3FFF resulta 0x2234
-sumarle 0xE4000 para obtener 0xE6234
-leer S45mem[0xE6234]. Suponer que vale 0x66
-leer S45mem[0xE6234+1]. Suponer que vale 0x55
-meter 0x5566 en R7, es decir:
-poner 0x66 en S45mem[0x00FBE4]
-poner 0x55 en S45mem[0x00FBE4+1]

En realidad es ms sencillo de lo que parece, una vez que le coges el truco a:
-trabajar en hexadecimal,
-little-indian
-segmentos
-acceso indirecto

Esto te puede llevar entre 2 das y 2 meses, dependiendo de lo que practiques.

*****************
QUITANDO TENSION
*****************
Para quitar la pesadez de cabeza, paso a un comando ms sencillo:
EC F6  = push R6
El C166 tiene una pila, como la mayora de los micros.
Slo admite 0x300 bytes (0x180 words) pero esto es ms que suficiente.
Al contrario que en otros sistemas, la pila slo se usa para almacenar la
direccin a la que hay que volver cuando finaliza una subrutina.
No se usa para guardar datos antes de llamar a la subrutinas, o antes de
modificarlos
Esto quiere decir que una rutina puede llamar a otra, que llama a
otra, ... un mximo de 0x180 veces.
Sin embargo el micro soporta todas las instrucciones normales de meter
y sacar registros y SFRs en la pila.
Hay un SFR llamado SP-Stack Pointer que dice dnde se guarda el siguiente dato.

Este SFR se almacena en 0x00FE12. Gracias a que es un SFR, se puede leer
sin problemas. ?Existe algn otro micro que permita leer el SP?
Que yo sepa, en otros hay que usar algn tipo de artificio.

Si SP vale 0xFBAC y R6=0x6789 y hago
push R6
entonces:
-en S45mem[0x00FBAC] se mete 0x89
-en S45mem[0x00FBAC+1] se mete 0x67
-la pila se decrementa en 2, es decir:
-SP=0xFBAC-2 = 0xFBAA
-y se almacena en 0x00FE12 :
-en S45mem[0x00FE12] se mete 0xAA
-en S45mem[0x00FE12+1] se mete 0xFB

Proceso anlogo con el comando   pop  , que saca un word de la pila.
Si meto demasiados datos en la pila y se alcanza el valor del SFR llamado
STKOV (almacenado en 0x00FE14) entonces se produce una interrupcin
StackOverflow sobre la que hablar despus.

El hecho de que la pile almacene la direccin de memoria a la que hay que
volver permite saber de dnde vengo, es decir, el camino que se ha seguido
hasta llegar a esta rutina.

Existe una rutina que es muy usada; se encuentra en E2FFFA y dice:
push R5
push R4
rets

Analizndolo bien, resulta que mete R5, luego mete R4, y retorna.
Dado que la direccin de retorno se obtiene sacando los dos ltimos registros
de la pila, en realidad lo que hace esta rutina es llamar a R5:R4
Esto se usa para acceso a rutinas a travs de tablas:
Suponer que quiero saltar:
-a rutina0 si R6=0
-a rutina1 si R6=1
-a rutina2 si R6=2
-a rutina3 si R6=3
La manera eficiente es crear
rutinas[]={rutina0, rutina1, rutina2, rutina3 };
Y saltar a
funciones[R6];

Ms claro:
-si rutina0 empieza en la direccin 0x00C01000
-si rutina1 empieza en la direccin 0x00C01040
-si rutina2 empieza en la direccin 0x00C01068
-si rutina3 empieza en la direccin 0x00C01246
A partir de 0xD00000 pongo los bytes:
00 C0 10 00    00 C0 10 40    00 C0 10 68    00 C0 12 46
y hago
mov r7, 0xD00000 ; posicin base de la tabla
add r7, r6       ; desplazamiento dentro de la tabla
mov r5, [r7+]    ; extrae los 2 primeros bytes
mov r4, [r7]     ; extrae los 2 segundos bytes
calls 0xE2FFFA

Bueno; este cdigo no es exacto, pero vale para hacerse una idea.

Como digo, este funcionamiento se encuentra al menos en 15 rutinas de la Flash.
Por tanto es recomendable hacer algo para aprovecharlo.
Lo que yo he hecho es: si el comando es  push R5, mira si estoy en la
direccin E2FFFA . Si es as, no me molesto en comprobar que la siguiente
instruccin es "push R4". Directamente saco los valores de R5 y R4, los
sumo R5*0x1000+R4, y ajusto IP para que salte a dicha direccin.
De este modo no tengo hacer el proceso de ajustar la pila.

*****************
SEGUNDA PILA
*****************
Pero a veces es necesario guardar los registros temporalmente.
Para eso se usa el registro R0.
La idea es usarlo como un puntero global a una zona grande de memoria.
Con la instruccin 
mov [-r0], r4
se guarda R4 en la direccin apuntada por R0 , y ste se decrementa.
Esto hace que apunte a una nueva direccin libre.
Con 
mov r4, [r0+]
lo que hago es restaurar r4 al valor que he guardado antes, e
incrementar R0 para seguir sacando ms datos.

De esta manera el Registro R0 opera como otra pila.
Por eso se ven muchas rutinas con instrucciones tales como:
mov [-r0], r4 ; guarda los registros originales
mov [-r0], r5
haz_algo_que modifique_R4_y_R5
guarda_resultado_en_algun_otro_sitio
mov r5, [r0+] ; recupera los registros
mov r4, [r0+]

Esto es simplemente para que entiendas cmo funciona.
A la hora de hacer el emulador, me sirve para saber que los flags PSW no
intervienen aqu, y no hay que ajustarlos.
Tambin me sirve para agrupar y ejecutar juntas todas estas instrucciones:
-miro todas las instrucciones seguidas que tengan que ver con R0.
-miro los registros GPR que sern guardados/recuperados
-calculo CP slo una vez
-calculo R0 slo una vez
-meto R4, R5 , ... y todos los que necesite
-actualizo R0 , dependiendo de el nmero de registros que he guardado.

Adems, todas las instrucciones
mov [-r0], Rn
suelen aparecer al principio de las rutinas, mientras que
mov Rn, [r0+]
aparecen antes de salir de la rutina.
Esto me sirve para identificar dnde empiezan y termina las rutinas, con
el propsito de identificarlas y aislarlas.

Incluso he hecho un pequeo analizador de cdigo en el que
-recorro toda la flash
-agrupo todas las instrucciones   mov [-r0], Rn
-substituyo la primera por una instruccin inexistente
-el emulador sabe que esta nueva instruccin define una secuencia especial
-procesa como un nico bloque todas esas instrucciones

Esto pre-proceso ahorra un montn de instrucciones, y lo he intentado
llevar hasta el extremo: s lo que hacen algunas rutinas, as que
las he sustituido por cdigo C "nativo".
Esto hace que el emulador slo valga para una versin especfica de
la Flash, y slo para este modelo de mvil. Pero la ganancia de velocidad
ha sido tan notable que prefiero hacerlo menos portable. Al fin y al cabo
todava funciona con otras versiones; simplemente no est optimizado.

*****************
ARITMETICA
*****************
Las operaciones aritmticas son tambin sencillas; por ejemplo:
06 F1 34 12 = add r1, #1234h
Suma 0x1234 al registro R1 y lo mete de nuevo en R1.
El segundo byte de esta instruccin es F1, al igual que el segundo byte de
E6 F1 34 12  = mov r1, #1234h
Esto es un hecho habitual en cdigo mquina: el primer comando dice la
operacin y el segundo dice los registros involucrados.
De esta manera queda claro que hay que hacer una rutina general que
divida el segundo operando y lo traduzca en los registros adecuados.

Otras instrucciones como SUB sirven para substraer cantidades, y tambin
hay otras como NEG para cambiarles el signo.
Lo que me sorprende es que no haya una para ajustes BCD, que es algo bastante
comn en microprocesadores de 16 bits.

As ya es fcil procesar otras instrucciones tales como AND, OR, XOR, NOT dado
que existen equivalentes en lenguaje C y no hay que construirlas. Simplemente
tener en cuenta si operan sobre registos, SFRs, datos directos, o indirectos.

Hay una instruccin que sirve para multiplicar: MUL
0B 23  = mul r2, r3
El resultado (32 bits) va a parar al SFR doble llamado MD, en la
direccin MDH=0x00FE0C y MDL=0x00FE0E
Para emularlo:
-tomar los valores de R2 y R3
-meterlos en variables long
-multiplicarlos
-tomar los 16 bits inferiores
-meterlo en S45mem[0x00FE0E]
-tomar los 16 bits superiores
-meterlo en S45mem[0x00FE0C]

Algo parecido con la instruccin DIV para dividir.
Otras instrucciones relacionadas son DIVL, para dividir un nmero de 32 bits
entre otro de 16 bits usando ambos MDH y MDL.
El emulador convierte todos los datos a long, hace las operaciones, y los
vuelve a convertir a enteros de 16 bits.
Son operaciones lentas, pero no son frecuentes. Menos mal que el emulador
funciona en un procesador que sabe hacer estas operaciones y no hay que
romperse la cabeza inventndolas.

*****************
LLAMADAS
*****************
Todos los programas necesitan reutilizar rutinas comunes. En el C166 esto
se hace con la instruccin CALLS
DA AB EF CD = calls 0ABCDEFh
El primer byte es la instruccin.
El segundo byte es el segmento (no la pgina)
El tercer y cuarto bytes son, en little-endian, el offset dentro del segmento.
Esta instruccin guarda en la pila la direccin de la siguiente direccin, y
sigue el proceso en la rutina 0xABCDEF.
Cuando se encuentre una instruccin RETS, se saca el ltimo dato de la pila, y
sigue el proceso donde lo haba dejado.
Esto tiene un riesgo: si en algn momento de la rutina 0xABCDEF existe un PUSH
sin su correspondiente POP, la pila contendr valores extra, y no retornar
a donde debera.
Este es uno de los fallos ms comunes al programar en ensamblador, como
muchos de vosotros habris padecido.

Otra instruccin similar es CALLR , que llama a una rutina dentro del mismo
segmento. Slo ocupa 2 bytes: el primero es la instruccin, y el segundo es
el numero de words que hay que saltar, ya sea hacia adelante o hacia atrs:

org      0C00040h
C00040: DA C0 46 00 : calls    dos_saltos
C00044:             : salto_atras:
C00044: CB 00       : ret
C00046:             : dos_saltos:
C00046: BB 02       : callr    salto_adelante
C00048: BB FD       : callr    salto_atras
C0004A: DB 00       : rets
C0004C:             : salto_adelante:
C0004C: CB 00       : ret

La instuccin en C00046 saltara a C00048+02*2 = C0004C
mientras que la instruccin en C00048 salta a 0xC0004A+0xFD*2-0x200 = 0xC00044
Por supuesto, antes de llamar a salto_adelante se guardar en la pila el
valor C00048 para saber a dnde hay que volver.
El beneficio de CALLR es que slo ocupa 2 bytes. El inconveniente es que
slo puede saltar 0x200 bytes hacia adelante o atrs.
Por esto no es una instruccin muy usada.

Similar es la instruccin de salto JMPS que salta a la direccin indicada
por los siguientes 3 bytes, de manera anloga a CALLS.
Por supuesto existe JMPR que salta a una direccin relativa a partir de
la situacin en la que estamos.

En el Sistema Operativo del mvil hay aproximadamente 15.000 rutinas, llamadas
en ms de 100.000 sitios.
Puede ser bastante complicado seguir el flujo de una rutina, sobre todo cuando
dependen de valores que estn almacenados en la memoria: alguna rutina pone
un valor, y otra completamente diferente los lee.
Para esto es conveniente usar una analizador de cdigo. Yo uso IDA disassembler
junto con algunos programillas que me he hecho a medida.
En general, tanto para la gente que hace programas como para los que los
desensambla, este es uno de los mayores problemas: " ?Por qu demonios hemos
aterrizado en esta rutina? "

Un uso muy comn de los saltos es que sean condicionales: se comprueba algo.
Si es cierto, salta a otra direccin.
Si no, continua en la siguiente instruccin:
                      org      0C00040h
C00040: 46 F3 33 33 : cmp      r3, #3333h
C00044: 3D 03       : jmpr     cc_Z, vale3
C00046:             : no_vale3:
C00046: E6 F4 44 44 : mov      r4, #4444h
C0004A: DB 00       : rets
C0004C:             : vale3:
C0004C: E6 F4 55 55 : mov      r4, #5555h
C00050: DB 00       : rets

O sea: se mira si r3 vale 0x3333 . Si es cierto, hace r4=0x4444 .
En caso contrario, hace r4=0x5555
Esto es fcil de entender para cualquiera que quiera entender el programa.
Pero para hacer el emulador, tengo que ver el significado de 'cc_Z'

*****************
FLAGS
*****************
Existe un registro SFR llamado PSW=Program Status Word almacenado
en 0x00FF10 que se trata bit a bit.
El bit 3 (el cuarto empezando por la derecha) se conoce como bit Z.
Se activa (vale 1) cuando el resultado de la ltima operacin es 0, o cuando
la ltima comprobacin es cierta.
As que procesar "jmpr cc_Z, vale3" es algo as como:
-leer PSW de la direccin 0x00FF10
-tomar el bit3 de PSW
-si vale 1, hacer IP=C00046+3*2=C0004C
-si no, hacer IP=C00046 (siguiente instruccin)
-seguir desde ah

Todo muy bonito, pero ahora hay que ver cundo actualizo PSW.
La instruccin "cmp r3, #3333h" es la que obviamente tiene que poner este flag.
La manera de hacerlo es:
-leer R3 , usando CP y toda esa parafernalia
-leer S45mem[0xC00040+2] y el siguiente, para obtener 0x3333
-Si son iguales, activar el bit 3 de PSW
-Si no, desactivarlo
Es sencillo, pero obliga a ms proceso en todas las instrucciones.
No slo eso, sino que en total hay 5 flags en PSW:
bit 0 , llamado N, que vale el bit 15 del resultado
bit 1 , llamado C, que indica que hay Carry ('me llevo una')
bit 2 , llamado V, que indica overflow aritmtico
bit 3 , llamado Z, que indica que el resultado es 0
bit 4 , llamado E, que indica que el resultado es 0x8000
Para complicar ms, algunas de las operaciones ponen slo algunos flags.
Por ejemplo, la instruccin NOP no modifica los flags.
Pero la instruccin CMP pone los 5 flags.
Y la instruccin MOV pone E, Z, y N. Los dems no los altera.
Un momento: ?o sea decir que tras cada MOV tengo que ajustar los flags? Pues s.
Esto aade un montn de proceso extra, y afectar muy negativamente a la
velocidad, as que hay que intentar optimizar todo lo posible.
El primer truco es usar bits en lenguaje C. Una vez que tengo R3 y el
valor a comprobar (llamado X) , tengo que meter el resultado en PSW, pero sin
alterar los otros bits:
if(R3==X)
 S45mem[0x00FF10] |= (1<<3);
else
 S45mem[0x00FF10] &= (0xFF-(1<<3));

Espero que lo entiendas:
-si R3 es igual a X :
--rota el numero '1' hacia la izquierda 3 veces para obtener 0x08
--lee S45mem[0x00FF10]
--hace un OR con 0x08. Esto pone nicamente el bit 3
--lo vuelve a poner en S45mem[0x00FF10]
-si no es igual:
--rota el numero '1' hacia la izquierda 3 veces para obtener 0x08
--lo invierte: los '0' pasan a ser '1' y viceversa.  Tambin podra usar el
  operador de complemento '~' pero no s en cual tecla est. Resulta 0xF7
--lee S45mem[0x00FF10]
--hace un AND con 0xF7. Esto borra nicamente el bit 3
--lo vuelve a poner en S45mem[0x00FF10]

Un poco ms complejo es tratar el flag N :
if((R3-X)&(1<<15))
 S45mem[0x00FF10] |= (1<<0);
else
 S45mem[0x00FF10] &= (0xFF-(1<<0));

Que es parecido al flag E :
if((R3-X)==0x8000)
 S45mem[0x00FF10] |= (1<<4);
else
 S45mem[0x00FF10] &= (0xFF-(1<<4));

Una optimizacin es crear un puntero a PSW y usarlo a lo largo del emulador:
char *PSW_low=S45mem[0x00FF10];
char *PSW_high=S45mem[0x00FF10+1];

......
*PSW_low |= (1<<0);
......

Otra manera de acelerarlo es intentar ajustar PSW slo en el momento que se
necesite: los saltos
As, si hay la siguiente secuencia:
mov r3, #3333h
sub r3, #9999h
mov r5, #5555h
cmp r5, #9999h
mov r4, r5
sub r4, #1111h
jmpr cc_Z, salta

entonces slo calculo los flags al ejecutar la instruccin "sub r4, #1111h"
Ojo: todas las otras instrucciones alteran los flags, pero machacan los flags
anteriores, por lo que slo me interesa la ltima.
Esto obliga a hacer un anlisis preliminar de la siguiente orden a ejecutar.
El proceso ya no es:
-lee instruccin
-ejecuta operacin
-salta a la siguiente

Sino que es
-lee instruccin
-ejecuta operacin
-lee siguiente instruccin
-si usa flags, ajstalos
-salta a la siguiente

Hay un grave inconveniente: ?Qu pasa si los flags no se leen inmediatamente?
por ejemplo:
mov r3, #3333h
sub r3, #1111h
cmp r3, #2222h
nop
jmpr cc_Z, salta

entonces la lgica no funciona, pues la siguiente instruccin es NOP, que no
altera los flags. Mala suerte; no hay un buen mtodo para prever dnde calcular
los flags.

Ahora veo que el mecanismo del C166 de guardar los SFR no es tan buena idea.
En otros emuladores tienen el mismo problema, aunque como no tienen que
guardarlo en un registro SFR, usan una variable interna al emulador.
Eso es malo para ellos.
Por ejemplo, yo tengo la instruccin
EC 88 = push PSW
que toma PSW desde S45mem[0x00FF10]
y lo mete en la pila. No tengo que tratar PSW como un dato especial.

En cambio otros emuladores tienen que usar un artificio para averiguar el
valor del registro de los flags.

*****************
CONTROL DE FLUJO
*****************
Como iba diciento, tratar las condiciones para los saltos es cosa de nios:
para procesar "jmpr cc_Z, salta"
slo tengo que mirar el bit 3 de PSW:
if (*PSW_low & (1<<3) )
 IP= IP+salta;

Bueno, como pocas cosas hay fciles en esta vida, las condiciones de salto son:
cc_UC  = incondicional. Siempre salta
cc_Z   = si el flag Z est activado
cc_NZ  = si el flag Z est desactivado
cc_V   = si el flag V est activado
cc_NV  = si el flag V est desactivado (el valor es negativo)
cc_N   = si el flag N est activado (el valor no es negativo)
cc_NN  = si el flag N est desactivado
cc_C   = si el flag C est activado
cc_NC  = si el flag C est desactivado
cc_EQ  = los valores son iguales
cc_NE  = los valores no son iguales
cc_ULT = Menor que ... (pero sin contar el signo)
cc_ULE = Menor o igual que ... (pero sin contar el signo)
cc_UGE = Mayor o igual que ... (pero sin contar el signo)
cc_UGT = Mayor que ... (pero sin contar el signo)
cc_SLE = Menor o igual que ... (considerando el signo)
cc_SGE = Mayor o igual que ... (considerando el signo)
cc_SGT = Mayor que ... (considerando el signo)
cc_NET = No igual y no fin-de-tabla

Las primeras 1+5*2 condiciones son evidentes.
Pero las otras son combinaciones de ellas. Por ejemplo, cc_ULE quiere decir que
-o bien son iguales: flag Z est activo
-o bien el segundo operador es menor que el primero: flag C est activo
As que la condicin
"jmpr cc_ULE, salta"
es:
if ( (*PSW_low & (1<<3)) || (*PSW_low & (1<<1)) )
 IP= IP+salta;
que tambin se puede escribir como
if ( (*PSW_low & (1<<3 | 1<<1)) )
 IP= IP+salta;

Para los que se han dado cuenta que (1<<3 | 1<<1)) vale 8+2=0xA , decir que
el compilador tambin lo sabe, y lo aplica eficientemente.

Para ms pena, todas estas condiciones de salto estn realmente usadas en
el SiemensS45, por lo que hay que implementarlas.
Y hay que hacerlo de manera que tarde el mnimo tiempo.

He visto que otros emuladores tratan los registros orientados a bits
con un "typedef struct" y una "union".
Es una tcnica muy buena pero desafortunadamente a m no me sirve porque
slo uso 5 bits, y el remedio es ms lento que la enfermedad.
Lo explico aqui para los que quieran aprender como se hara:

typedef struct _flagsPSW
{
 unsigned ILVL:4;
 unsigned IEN:1;
 unsigned HLDEN:1;
 unsigned noUsado9:1;
 unsigned noUsado8:1;
 unsigned noUsado7:1;
 unsigned USR0:1;
 unsigned MULIP:1;
 unsigned E:1;
 unsigned Z:1;
 unsigned V:1;
 unsigned C:1;
 unsigned N:1;
};

typedef struct _bytesPSW {
 char	high;
 char	low;
};

union _PSW {
struct _flagsPSW flagsPSW;
struct _bytesPSW bytesPSW;
int intPSW;
};

union _PSW PSW;

PSW.flagsPSW.Z=0;
PSW.bytesPSW.high=0;
PSW.intPSW=0;

Pero como he dicho, el orden de los bits, bytes, y long es dependiente de
la mquina sobre la que compilo el emulador y esto altera completamente
el resultado.

Ah, como nota curiosa, decir que
mov PSW, r3
funciona perfectamente, y es una manera correcta de poner los flags.
De hecho, este comando est varias veces usado en la Flash del Siemens S45.

A propsito de esto, una instruccin bastante potente es CMPI2
96 F4 44 44  = cmpi2 r4, #4444h
que significa:
-compara r4 con 0x4444
-ajusta todos los flags
-incrementa r4 en 2 unidades, pero sin alterar los flags
-toma el flag Z calculado anteriormente para calcular condiciones de salto
Es un comando bastante usado para recorrer tablas y bucles

Tambin existe una instruccin CMPI1 que lo incrementa slo en 1 unidad.

******************
DATOS INNECESARIOS
******************

Otra instruccin es CPL, que complementa a 1 el operador.
91 30 = cpl r3
Si r3 vale 0x3333, tras esta instruccin valdr 0xFFFF-0x3333 = 0xCCCC
Lo gracioso es que slo se ajustan los flags E, Z y N.
Los flags V y C siempre se ponen a 0.
Esto me obliga a que la rutina que emula el ajuste de los flags tiene que
ser flexible para admitir nicamente algunos de los flags.
Por otra parte esto es bueno, pues me ahorra algunos clculos.

Como se puede ver, el cdigo de esta instruccin es 0x91, y el siguiente
byte (0x30 en este caso) indica el registro GRP que hay que usar.
Puesto que slo hay 16 registros GPR, este dato vale 0x00 (para R0),
0x10 (para R1), 0x20 (para R2), ... 0xF0 (para R15), as que la parte
baja del segundo byte no sirve para nada.
Esto hace que tambin la instruccin
91 38 signifique "cpl r3"
y lo mismo en general con 91 3n

Esto pasa con muchas de las instrucciones. No necesitan todos los datos.
Pero tampoco es cuestin de ahorrar innecesariamente.

***********************
INSTRUCCIONES FANTASMAS
***********************
Segn el manual, si una instruccin no est implementada, el C166 debera
saltar (con CALLS) a una rutina concreta 0x000004) para procesar como si fuera
una excepcin crtica , ya que no deberan ser parte de un programa normal.

Esto slo funciona con el primer byte.
Por ejemplo, la instruccin NOP es CC 00
Si hago un programa que contenga
CC 01
esto debera saltar a la rutina en 0x000004. Bueno, pues no lo hace.
En cambio, el cdigo 8B no corresponde a ninguna instruccin.
Si mi programa tiene
8B 00
entonces s que salta a 0x000004.
En condiciones normales este programa resetea el mvil, entendiendo que ha
habido un error y la instruccin ejecutada no debera estar ah.
Pero cambiando este programa se puede ampliar el conjunto de instrucciones.
Algo as como:
org 0x000004
pop r4 ; como me llaman con CALLS, la pila tiene posicin+2 desde la que vengo
push r4 ; lo vuelvo a poner, pues lo necesito para retornar
mov r3, [r4-2]
cmp r3, 0x8B00
jmpr nn_NZ, no_es_8B00
 ; hacer algo especial
 rets
no_es_8B00:
 rets ; (o mejor: RESET)

As se puede hacer fcilmente una extensin al conjunto de operaciones.

La manera de gestionar esto en mi emulador no es complicada:
si no consigo averiguar cual es la instruccin para el cdigo, salto a 0x000004.

Si en el proceso inicial slo cargo la Flash a partir de la memoria 0xC00000,
seguro que no habr ningn programa en 0x000004. Pero como lo que yo tengo
es la copia de la memoria desde 0x000000 hasta 0xFFFFFF, es posible que
alguna de las rutinas de inicializacin haya puesto algo all.
Efectivamente, lo que hay es un salto a CDE4AE.
Esta rutina se encarga de volcar la pila a 0x10DACE, y luego salta a CDE4EC.

Aqu pone la pila con valores seguros, resetea casi todos los registros, guarda
los datos desde 10DACE a una zona permanente de la memoria, y resetea el mvil.
Esto permite que se pueda analizar a posteriori dnde ha encontrado la
operacin errnea.

*****************
T'HAS PASAO
*****************
Como recordars, la memoria va hasta 0xFFFFFF, pero es posible hacer
org 0xFFFFFC
0D 08
que significa:
jmpr cc_UC, 0x8
Con lo que intentar saltar a 0xFFFFFC+0x8=0x1000004 que est fuera de memoria.
Para el mvil lo mejor es saltar a otra rutina que gestiona accesos a memoria
ms all de los lmites.
Por condiciones de diseo esta es la misma rutina 0x000004.
Yo tengo que emular lo mismo; tras cada salto con jmpr y callr:
if (IP>0xFFFFFF)
 IP=0x000004;

Otro problema es que al ser un micro de 16 bits slo puede saltar a
direcciones mltiplos de 2. No es legal hacer
calls 0xC00001
Esto yo lo soluciono haciendo
if (! IP%2)
 IP=0x000004;

Similarmente no es posible leer un word de una memoria de posicin impar.
mov r3, 3333h ; no es admisible; leera 2 bytes de la memoria en 0x3333
Notar que es perfectamente normal leer un byte:
movb rh3, 3333h
s es vlido, pues:
-se leer el byte de la memoria 0x3333
-suponer que es 0x55
-se guardara en RH3, es decir, S45mem[0x00FBD6+3*2+1]

Algo parecido pasa con el StackOverflow. Si meto en la pila ms datos de
los que caben, se produce una excepcin y se salta a la rutina 0x000004.

Pero claro, estos chequeos ralentizan el procesado. Al fin y al cabo, ?cual
es la posibilidad de que los ingenieros de Siemens hayan cometido un error y
salten a una direccin impar? Mnima. As que decido eliminar estos controles.

****************************
SOLO ALGUNOS SEGMENTOS
****************************
En los prrafos anteriores he dado por supuesto que la memoria ocupa 16 Mg.
Bueno, esto no es del todo cierto.
La memoria total se divide en 0x400 pginas de 0x4000 bytes , de las
cuales 0x100 , es decir, 4 Mg son de RAM.
El resto es para RAM, pero no toda es real.

Slo algunos de los segmentos existen en realidad: los dems son "espejos" de
los dems. Por ejemplo, el segmento 0x0100 es el mismo que el 0x0200.
A decir verdad, apenas 160 (0xA0) se usan, en vez de 768 (0x300)
Los 0x0020 primeros son reales. Pero desde 0x0020 hasta 0x0040, son los mismos
que desde 0x0000 hasta 0x0020
Tambin desde 0x0040 hasta 0x0060 son los mismos. Y desde 0x0060 hasta 0x0080 .
Pero es posible leer y escribir en todos ellos. Simplemente que se comportan
como si fueran el mismo.
La manera de tratarlo es sencilla:
si la pgina de memoria a escribir es mayor que 0x0020 y menor que 0x0080 ,
entonces toma la pgina, mdulo 0x0020.
Esto introduce un paso ms cada vez que accedo a la memoria.
Bueno, puedo evitar chequear esto cuando accedo a los registros SFR y GPR, ya
que s que siempre caen en la pgina 0x0003, con lo cual me evito el 90% de
las comprobaciones.
Otra manera ms eficiente es:
pgina &= 0x001F
dado que esto reducir la pgina a un valor comprendido entre 0x0000 y 0x0020.
Ligeramente mejor es usar un array de pginas de las que slo uso
las 0x200 primeras, y las otras son simples punteros a estas.
char *paginas[0x400];
for(i=0;i<0x0020;i++)
   {
   paginas[i]=malloc(0x4000);
   paginas[0x0020+i]=paginas[i];
   paginas[0x0040+i]=paginas[i];
   paginas[0x0060+i]=paginas[i];
   }

Para acceder a una posicin de memoria en la pgina 0x56 y offset=0x7890 hago
paginas[0x56][0x7890]

en general, para acceder a un dato en la memoria debo hacer
pagina=posicion/0x4000;
offset=posicion%0x4000;
dato=paginas[pagina][offset]
con el beneficio extra de que slo uso 1/4 de la memoria.

Algo todava mejor es evitar esos clculos sobre "posicion" usando una
estructura para separar automticamente los bytes:
typedef struct _posicionBytes {
 char	b1;
 char	b2;
 char	b3;
 char	b4;
};

union {
 _posicionBytes posicionBytes;
 long posicionLong; 
 };

Pero esto es totalmente dependiente si el procesador destino es little-indian
o big-indian. Puede que funcione en un Pentium pero no en SPARC.
Ya s que el compilador es capaz de detectar esto, pero a pesar de todo aade
complejidad al leerlo.

Como creo haber dicho anteriormente, el propio compilador debera ser
capaz de saber que 
pagina=posicion/0x4000;
se puede calcular ms rpidamente haciendo
pagina=posicion>>14;
y que
offset=posicion&0x3FFF;

Otras pginas tambin estn duplicadas: todas las pginas entre 0x0100
y 0x0180 son las mismas que entre 0x0080 y 0x0100
En general, cualquier pgina mayor que 0x100 y menor que 0x300 es
equivalente a tomar la pgina%0x0080+0x0080.
Es decir, slo existen 0x80 pginas reales.

*****************
EVITAR SOBRECARGA
*****************
Obviamente el emulador contiene un bucle principal para identificar
instrucciones, unas 80 funciones para ejecutar cada uno de los tipos
de comandos, y otras funciones comunes para:
-leer/escribir un registro SFR
-leer/escribir un registro GPR
-leer/escribir un word/byte en la memoria
-averiguar un segmento
-hacer uso del DPPi
-leer/escribir flags

Una manera de evitar sobrecargar el programa es usar funciones inline.
El compilador entonces no genera una funcin, sino que usa el cdigo
generado una y otra vez, incluyndolo en el cdigo final. Esto aumenta
el tamao del programa ejecutable, pero evita el proceso de llamar
a las funciones.

Otra mejora es evitar pasar parmetros a las funciones.
Suponer la instuccin
00 45 = add r4, r5
el primer byte 0x00 indica que es la instruccin ADD para sumar dos SFR
mientras que 0x45 indica que el fuente es R5, y el destino es R4
Podra hacer:
 registros_a_usar=0x45;
 nibble_bajo=tomar_nibble(registros_a_usar, PARTE_BAJA);
 CP=calcula_GPR("0x00FE10");
 valorR5=leeSFR(CP, nibble_bajo);
 nibble_alto=tomar_nibble(registros_a_usar, PARTE_ALTA)
 valorR4=leeSFR(CP, nibble_alto);
 valorR4+=valorR5;
 escribeSFR(CP, valorR4);

Ms eficiente es:
-crear variables globales que reuso una y otra vez
-evitar variables temporales
-usar menos funciones, pero ms grandes
-pasar menos argumentos:

 global_registros_a_usar=0x45;
 CP=calcula_GPR("0x00FE10"); /* calcularlo las mnimas veces posible */
 Suma_y_escribeSFR(
           leeSFR_usandoCP_y_nibbleBajo(),
           leeSFR_usandoCP_y_nibbleAlto()
           );
donde
-leeSFR_usandoCP_y_nibbleBajo sabe que tiene que usar:
---el nibble bajo de la variable global_registros_a_usar
---la variable global CP
-leeSFR_usandoCP_y_nibbleAlto sabe que tiene que usar:
---el nibble alto de la variable global_registros_a_usar
---la variable global CP
---retorna el valor, y deja la direccin (0x00FBD6+4*2) en ultimoR
-escribeSFR tiene que sumar, y escribir usando:
---los parmetros
---meter en nibbleAlto el valor *ultimoR

Por supuesto que esto hace que muchas de las rutinas deban estar duplicadas, o
tenga funcionalidades muy parecidas. Pero esto se soluciona haciendo macros.
Algo as como
#define xxx(global_registros_a_usar & 0x0F) xxx_nibbleBajo()
#define xxx(global_registros_a_usar >> 0x4) xxx_nibbleAlto()
y viceversa, segn quiera agrupar instrucciones o separarlas.

****************************
DE REPENTE, EL ULTIMO VERANO
****************************
Hay algunas instrucciones que las nicas personas que las usan son los
programadores de Sistemas Operativos y de emuladores. Entre ellas estn:
PWRDN:  apaga el mvil
SRST:   resetea el mvil
EINIT:  fin de inicializacin: la pila y los registros tienen valores seguros
IDLE:   entra en modo de bajo consumo. Interrumpible por interrupcin hardware
SRVWDT: servicio del watchdog. Cada cierto tiempo el mvil tiene que decir que
        sigue vivo. Si no, el hardware provoca un reset.
DISWDT: deshabilito el watchdog, normalemente porque ya estoy respondindole
Estas instrucciones son muy importantes para la multitarea.
Cuando el mvil no tiene nada que hacer, entra en modo IDLE. Entonces slo
una seal de hardware puede despertarle.
Adems de esto, se pueden producir otras seal en cualquier momento:
-cuando un dato se recibe por el puerto serie
-cuando un dato se recibe por el interface de radio
-cuando el timer alcanza un valor
-cuando se pulsa una tecla
-cuando el micrfono detecta sonido
-el puerto de infrarrojos recibe un dato
-el cable (de datos, batera, coche) se conecta
-la batera est baja

estos eventos se procesan a travs de una tabla de interrupciones que se
encuentra a partir de 0x000008

En cierto modo, el fallo "instruccin no implementada" y "fuera de memoria"
tambin actan como interrupciones.
Cada evento salta a una direccin adecuada. Por ejemplo, el timer va a 0x0000A4
Para emular esto tengo varias opciones:
-saber el tiempo exacto que debera tardar en ejecutarse cada instruccin.
 Cuando llegue a un cierto lmite, salto al handler de 0x0000A4 . Esto me
 obliga a hacer unos cuantos ms clculos
-establecer un timer en mi emulador. Para esto debera usar libreras
 de C especficas al sistema operativo en el que corre mi emulador.
-cada X instrucciones procesadas, llamar al handler. El control de tiempos
 no es exacto, pero es fcil de implementar, as que me decido por esta opcin.
Tengo algo que no funciona exactamente a 25 Mhz, pero al menos lo parece.
Ya que estoy en este apartado, decir que consigo una velocidad 1000 veces
menor: en un PC a 2.5 GHz, el emulador va 10 veces ms lento que el mvil
autntico. A m me sirve as. Pero estoy gratamente sorprendido de que
el emulador de Palm pueda alcanzar velocidad en tiempo real.

La mayora de los otros eventos son muy difciles de implementar: por
ejemplo, ?como voy a simular el micrfono, si mi PC ni siquiera tiene uno?
Adems, no s como programarlo y no me apetece estudiarlo.
Lo que s puedo hacer es preparar mens para simularlos. Por ejemplo, diseo
un botn que simula el puerto del C166 que dice que la batera est baja.
Lo fundamental es que todo esto est bien gestionado en el mvil. Si quiero
saber cmo funciona en la realidad, debo analizar en vivo su funcionamiento.
Esta es la misma tcnica que usa el otro simulador SMTK.
En otros emuladores esto tampoco est completamente resuelto. Por ejemplo, el
emulador de Palm usa el ratn en vez de la pantalla tctil y el stylus, pero
no puede simular el puerto serie.

Al igual que hay interfaces de entrada, tambin los hay de salida:
-pantalla
-iluminacin de pantalla
-vibracin
-altavoz
-puerto serie
-puerto infrarrojos
-interface de radio
-tarjeta SIM
De estos, lo ms til de emular es la pantalla.
Tras breves investigaciones llegu a la conclusin de que la memoria del
display est almacenada a partir de 0x005FD4 y ocupa 60 lneas de 13 bytes, en
la que cada bit es un pixel, de izquierda a derecha.
Al menos no es tan complejo como Wine, donde hay que usar planos de colores.

El resto de los interfaces no los he implementado. Cuando se manda un dato
a ellos, simplemente lo imprimo en una subventana.

En mi opinin sto es lo que marca el xito de un emulador: el hardware que
es capaz de simular.
Por eso es tan "fcil" simular otro ordenador. Al fin y al cabo casi todos
tienen teclado, ratn y pantalla, ?no?

Una solucin que me gustara implementar es usar el sistema real, conectado al
sistema emulador.
Por ejemplo, si quisiera mandar algo al altavoz real, me conecto al mvil (por
el cable serie) y le digo que active el altavoz.
El inconveniente es que sto exige mucha interaccin a alta velocidad, y
precisamente velocidad es lo que me falta.

Por supuesto que me gustara simular el interface con la tarjeta SIM o el
de radio, pero creo que tardar en hacerlo.

*************************
LA NOCHE DE LOS TRAMPOSOS
*************************
En otro artculo expliqu cmo funcionan los TRAPs. Para no repetirme, dir
que es una manera cmoda de llamar a una subrutina.
9B 54 = trap #2Ah
saltar a la rutina en 0x2A*4=0x0000A8
Esta rutina resulta ser un
jmps 0CE3468h
que lee ADDRSEL2 , o sea, uno de los interfaces.

Las rutinas llamadas por "trap" siempre deben acabar con "reti", no con "rets"
Para simular "trap nn", hay que hacer
push PSW
push CSP
jmps nn*4
Como el byte que sigue a la instruccin 9B slo usa nmeros pares, en realidad
el valor ya est multiplicado por 2.
Por eso slo hay 0x80 traps, que saltan a direcciones mltiplo de 4.

*****************
EVITANDO DPP
*****************
Antes he explicado el espinoso asunto de usar los registros DPPn para el modo
de direccin largo.
A veces slo hay que leer un dato, y no interesa cambiar DPPn.
Para esto se usa el comando EXTP, en el que se extiende la pgina indicada
D7 40 42 00 = extp #42h, #1
F2 F6 66 66 = mov  r6, 6666h
Con esto, R6 tendr el valor que est en la pgina 0x0042 , offset 0x6666 , es
decir, el valor de
0x0042*0x4000 + 0x6666 = 10E666
O sea, R6=S45mem[0x10E666]

Para emular esto lo que tengo que hacer es deshabilitar temporalemente el
uso de DPP0, lo que me obliga de nuevo a procesar las instrucciones siguientes
a "extp" antes de ejecutarlas. Felizmente esto solo lo tengo que hacer
cuando encuentre la instruccin EXTP.
O sea: 
-cuando encuentro extp , poner una variable global llamada G_extp.
-antes de usar un GPR, mirar si G_extp est puesto
-si es as, usarlo, en vez de DPPn
-si no, usar DPPn
De nuevo, algo que ralentiza el procesado :-(


Bueno, tambin existe la instruccin EXTR para acceder a otros registros SFR
que estn situados en una memoria externa llamada "Espacio SFR Extendido", pero
slo se usa en una rutina, para acceder a la memoria de los perifricos, as
que ni siquiera me he molestado en implementarla.

Hay otra instruccin EXTS para extender el segmento. Funciona igual que EXTP,
pero con segmentos en vez de pginas. No se usa nunca.

En este caso que no implemento una instruccin, lo que hago es mostrar un
aviso en la consola. As s que tengo que andar con cuidado, pues puede
suceder que los datos sean inconsistentes a partir de dicho momento.
Es un riesgo que puedo tomar sin mayores quebraderos de cabeza.

******************
PROBANDO, PROBANDO
******************
Por supuesto, yo he hecho cientos de programas para verificar que mi emulador
procesa instrucciones de manera igual al mvil real.
Tambin he llamado a rutinas complejas del mvil, y comprobado que el resultado
es el mismo que en el emulador.

Lo ms fcil es cuando la rutina apenas depende de datos externos. Pero muy a
menudo una rutina pone un valor, y 3 rutinas ms all se lee dicho valor.
Por no contar las rutinas que preparan datos, los guardan en memoria, y salen.
Ms tarde el controlador de tareas ve que hay algo pendiente y contina el
proceso. Esto es muy comn en el C166.

Pero resulta indescriptible la sensacin cuando pones a trabajar al emulador
por un lado, y al mvil por otro, y al cabo del rato finalizan la ejecucin
dando el mismo resultado.
Esto implica mltiples volcados de memoria desde el mvil hacia el PC, pues la
ms mnima diferencia hay que estudiar porqu se ha producido, y dnde.

Entonces hay que relanzar la simulacin hasta que coincida con el sistema real.
Dado que no es posible iniciar el telfono en estados idnticos, cada rutina
ejecutada puede actuar de modo distinto si lo ejecutas en un momento o en otro.
Hay tantas condiciones externas que resulta difcil controlarlas todas.
Y eso que yo "simplemente" he hecho un emulador. Imagina los tcnicos
de Siemens cuando han tenido que desarrollar el sistema.
Claro que ellos cuentan con mucha ms experiencia, medios tcnicos, y adems
les pagan por ello.
De todos modos, desde aqu mi admiracin para todos ellos y los miles de
profesionales que hacen su trabajo a conciencia.
Igualmente felicidades a todos los aficionados (significando: sin paga) que
dedican tiempo y esfuerzo a la investigacin, en cualquier campo de actividad.

*****************
UNIVERSALIDAD
*****************
Con esto consigo un sistema capaz de emular cualquier telfono Siemens
que lleve un microprocesador C166.
Esto me permite emular un S45, o el C35.
Tambin los modelos SL45i, S55, A55, S2, y otros 10 ms.
Hay pequenios detalles que diferencian unos de otros; en general la
ubicacin del memoria de pantalla y los diversos puertos.
Puesto que no he emulado los puertos, esto no es un problema.
Lo peor viene porque no dispongo de ningn otro modelo, as que no puedo
probar cosas tan importantes como el nmero de segmentos, el tamao de
memoria, o el funcionamiento del sistema de interrupciones.

Modelos diferentes tienen comportamientos diferentes. Lo bueno es que se
puede entender rpidamente un modelo, si ya has llegado a comprender otro.

Adems lo nico que he encontrado es la Flash, pero necesito la memoria
completa de un sistema que est funcionando.
Supongo que otros creadores de emuladores piden a la gente que les manden
copias de sus ROM, que hagan de beta-testers, o les manden sus sistemas.
Yo lo he hecho y la respuesta recibida ha sido mnima. Quizs mi sistema
es todava muy frgil o poco user-friendly, y pocos han conseguido obtener
copias fiables de sus sistemas.
No que quejo: simplemente indico que si quieres que algo se haga, lo mejor es
que lo hagas t mismo.

A propsito de esto, existen emuladores de Java para casi todos los modelos
de Siemens. En teora slo sirven para probar los applets, pero el sistema
de navegacin de mens hace pensar que internamente incluye el mismo
Sistema operativo, pero dentro de un programa. Las instrucciones no son
las mismas; en otras palabras, la Flash del mvil no est dentro del
emulador. Yo creo que han tomado el cdigo fuente (escrito en C, casi seguro)
del S.O. del mvil, y lo han compilado para PC.
Luego se aade un interface grfico, y se substituyen la rutinas de acceso
a ficheros, SIM y radio por otras simulaciones gobernadas por mens.
Claro que es mucho ms difcil que esto, como supongo que va quedando
evidente a lo largo de este texto.

*****************
DEBUGGER
*****************
Tal como he mencionado al principio de este artculo, el objetivo era
desarrollar un sistema que me permita probar los programas que yo meto en
el mvil, antes de transferirlos definitivamente.
Pero claro, la mayora de las veces ni siquiera funcionan en el emulador.
Una vez descartado los fallos de programacin del emulador en s, hay
que identificar los fallos de mis programas.
La herramienta ms til es un debugger, que me permita
-seguir el flujo de ejecucin del programa
-consultar y cambiar los valores de los registros
-mirar la memoria
-detener el progama cuando una cierta condicin sea cierta.

Para ello he implementado un sistema de visualizacin y edicin en
multiventanas, donde puedo ver y modificar todo lo que quiera, incluyendo:
-registros
-cdigo desensamblado de las instrucciones
-memoria
-datos en binario, hexadecimal, y ASCII
-pila
-pila de R0
-flujo de llamadas

y un sistema de debugging:
-poner/quitar/ver breakpoints en rutinas
-lo mismo, en rangos de memoria
-tambin en valores de registros GPR
-registros SFR
-posiciones de memoria

Aqu seguro que hay muchos trucos, pero yo no uso casi ninguno.
La nica optimizacin que he desarrollado es la bsqueda de breakpoints.
En vez de mantener una lista con todos ellos, y recorrerla antes de ejecutar
cada instruccin, he decidido crear un array de 16Mg: si el dato
contiene 0x01 entonces hay un breakpoint.
Pongo un 0x02 si hay un breakpoint de valor GPR o SFR. Al fin y al cabo, son
posiciones de memoria, ?no?
Cuando ejecuto una instruccin o cambio un dato , por ejemplo en IP=0xFCA000
miro si breakpoint[0xFCA000] !=0 y detengo el programa para empezar la
investigacin. Esto es rpido y terriblemente eficiente.

Tan importante como preparar los breakpoints es poder guardarlos, junto con
el estado del programa. Esto es fcil: guarda la memoria emulada
del S45mem[] y breakpoint[].
Total, 64 Mg. no es tanto. Y si slo guardo los segmentos que en realidad
estn usados, mucho mejor.

*****************
DEMASIADO RAPIDO
*****************
Para conseguir la mayor velocidad en un sistema como Windows o X-window, es
preciso procesar muchas instrucciones sin detenerse a mirar otros eventos.
Esto implica no mirar si el ratn se ha movido, o si algn men se ha
elegido, o si algn botn se ha pulsado.
Esto hace que algunos emuladores usen toda la CPU no dejando que otros
programas funcionen a la vez. Realmente, no soy capaz de encontrar una solucin
que sea buena: o miro constantemente otros eventos, o no los miro casi nunca.
Como medida preventiva miro los eventos cada 1000 instrucciones, lo cual es
ms que suficiente.
Otra solucin es mirarlos cada vez que se produce una cierta instruccin, por
ejemplo   rets , que se produce bastante frecuentemente.
Como he dicho, los breakpoints se miran a cada instruccin ejecutada, as
que es imposible que pierda ninguno.

Al usar la herramienta "profiler" he visto que podra mejorar todava ms
la eficiencia si mantengo cacheados los registros IP , CP, R0 y SP .
Como apenas existen instrucciones que los modifiquen directamente, me puedo
permitir el lujo de tener punteros a ellos, y escribirlos slo cuando veo
que alguien va a leerlos.
Esto result en una mejora del 40%. No estoy seguro de que funcione
perfectamente siempre (de hecho, s cmo hacerlo fallar) pero hasta
ahora no he tenido problemas.

Esto me oblig a reescribir algunas partes del emulador, en puntos que
consideraban que deban reajustar los registros, cuando en realidad
no era absolutamente necesario.

******************
QUE SERA, SERA
******************
El siguiente paso que quiero hacer es un debugger en tiempo real del mvil.
El emulador ejecutar las rutinas que sea capaz, y le pedir al mvil que
ejecute las que no pueda.
As quizs pueda conseguir un sistema hbrido para hacer mis pruebas sobre
la parte de telefona.

El modelo S45 es bastante potente. Ahora, al final, me pregunto si debera
haber usado otro ms pequeo, ya que tendr menos funcionalidad que estudiar.
Las rutinas de GPRS, Internet, FlexMem, salvapantallas, ... no hacen ms que
complicar el anlisis y visin compacta del sistema.

Otra posibilidad es adquirir un modelo superior; por ejemplo el S65 que tiene
cmara, Bluetooth, pantalla de colores, Java, polifona, MMC, y 16 Mg de Flash.

Aunque tambin es posible que deje reposar estos temas durante un tiempo.
El merecido descanso del guerrero.

*EOF*