Haré una breve reseña de cómo obtener e instalar el simulador THRSim, excelente herramienta para la programación Assembler. Funciona bajo Windows, versiones de 32 y 64 bits, testeado incluso en Windows 10.
Se puede descargar desde la Web oficial del THRSim. Solo tener en cuenta un punto: en la web encontrarán:
Download THRSim11 with C support for free
Download THRSim11 without C support
Aunque el primer instinto es descargar desde el primer link, se han reportado muchos casos de fallos, particularmente al ingresar a las opciones del menú "Memory" y en equipos de 64 bits. Por tanto mi humilde consejo: descargue e instale "without C support".
La instalación no tiene mayores secretos, puede darle al "siguiente, siguiente" y en pocos instantes tendrá la aplicación funcionando.
Para empezar a trabajar no hace falta hacer nada más. Tal vez algún puritano quiera ver la configuración de la memoria del HC11 simulado, esto se puede hacer desde el menú View -> Memory -> Memory Configuration... a tener en cuenta que se cerrará el programa (por tanto habría que guardar cualquier archivo que querramos preservar).
Luego de un aviso y de cerrar la aplicación podemos ver (y opcionalmente modificar) la configuración de la memoria:
En rojo marqué los rangos de memoria RW y RO que usaremos en los ejemplos del blog (en los valores por default). Notar sin embargo que se pueden configurar más secciones de memoria, tanto RW (aunque diga RAM, ya todos sabemos que toda la memoria del HC11 es random access) y RO.
No veo necesidad inmediata de aclarar más aspectos de la instalación, pero si tienen dudas coméntenlas y agregaré el contenido que sea necesario.
Llegamos a la tercera parte de esta novela de romance y bits. ¿Que dónde está el romance? Mmm... ¿no lo viste? Seguí practicando hasta que veas la danza binaria entre acumuladores...
Este es el programa desensamblado:
$C000 7F 00 03 CLR $0003
$C003 7f 00 04 CLR $0004
$C006 de 00 LDX $00
$C008 4F bucleCLRA
$C009 E6 00 LDAB 0,x
$C00B D3 03 ADDD $03
$C00D DD 03 STD $03 $C00F 08 INX $C010 7A 00 02 DEC $0002 $C013 26 F3 BNE bucle $C015 20 FE fin BRA fin
Con esto nos podemos dar una idea de cuál es su propósito, pero el trabajo aun no termina: había una porción de memoria que aun no procesamos:
$0000: 00 06 05 01 02 03 04 99 54 65 32 Además en el programa estamos usando direcciones de memoria para acceder los operandos cuando quedaría más prolijo y claro utilizar etiquetas. En primer lugar veamos cómo deducir el tamaño y cantidad de los operandos:
Las instrucciones con operandos de 8 bits están marcadas en rojo. Las que manejan operandos de 16 en azul. ¡Cuidado! No debemos confundir operandos de 8 o 16 bits con direcciones de 8 o 16 bits. En modo directo solo manejamos la parte baja de la dirección, o sea 8 bits de ella, y en modo extendido los 16 bits de la dirección. Pero, podemos emplear modo directo y trabajar con 16 bits (por ejemplo, ADDD $03) o trabajar en modo extendido y operar con 8 bits (por ejemplo DEC $0002). Empecemos con las marcadas en azul, las de 16 bits. Se carga IX con lo que hay en $0000 (es modo directo, por tanto la dirección completa es $0000). Podemos por tanto determinar que en $0000 hay una dirección de 16 bits, llamémosla dir1. También se realiza una suma de 16 bits con almacenado en $0003 (por lo tanto ocupa $0003 y $0004). Ahí tenemos un operando de 16 bits, podemos llamarlo double1. Nos quedan los operandos de 8 bits. Pero observe que aunque los dos primeros CLR del programa trabajan en 8 bits, realmente se está limpiando el double1 en mitades. Queda entonces la carga del elemento apuntado por IX, que se realiza leyendo 8 bits y cargándolo en el acumulador B. Por tanto lidiamos con un vector de elementos de 8 bits. El último operando de 8 bits es el que utilizamos con la instrucción DEC $0002. Llamémosle byte1. Note que luego de cada decremento se verifica si llegó a cero, y en caso de que eso no ocurra se repite el bucle. De esto se desprende que se trata de un contador, que inicialmente contendría la cantidad de elementos del vector. Pasemos en limpio las reservas de memoria y comparémoslo con el contenido de la memmoria a partir de $0000:
$0000 00 06 dir1 RMB 2 <- direccion inicial del vector $0002 05 byte1 RMB 1 <- cantidad de elementos del vector $0003 01 02 double1 RMB 2 <- ¿ya te diste cuenta? $0005 03 04 99 54 65 32
La dirección inicial del vector es $0006 ¿qué hay en la dirección $0005? Solo una posición de memoria sin usar. Llamémosle caza pichones. Lo mismo aplica al contenido inicial de double1. El vector tiene los elementos (04,99,54,65,32), ya que la dirección dir1 y la cantidad byte1 así lo marcan.
Empleando los nombres de los símbolos nos queda esto:
$C000 7F 00 03 CLR double1
$C003 7f 00 04 CLR double1 +1
$C006 de 00 LDX dir1
$C008 4F bucleCLRA
$C009 E6 00 LDAB 0,x
$C00B D3 03 ADDD double1
$C00D DD 03 STD double1 $C00F 08 INX $C010 7A 00 02 DEC byte1 // cantidad $C013 26 F3 BNE bucle $C015 20 FE fin BRA fin Queda la frutilla del postre: ¿qué hace el programa? Tomese un momento para repasar: Sabemos que:
procesa un vector de elementos de 8 bits, del que se conocen dirección inicial y cantidad de elementos
efectúa una sumatoria en 16 bits
guarda un resultado de 16 bits
Tal vez el único detalle hasta ahora no esclarecido es que los elementos se leen en 8 bits en el AccB y justo antes se limpia AccA, de esta forma cada operando de 8 bits se puede manejar como de 16. En lenguaje C hablaríamos de un cast.
¡Resuelto! Tomó un rato pero lo logramos.
Uso de THRSim para desensamblar ¿Cómo podríamos utilizar el simulador para verificar que estamos en lo correcto? Relativamente fácil: utilice la opción Memory Dump para cargar las dos porciones de memoria provistas inicialmente. Tendría que quedar así (combiné dos capturas de la ventana Memory Dump):
Paso importantísimo: verifique el valor del PC, debiera ser $C000. Ahora vamos al menú View -> Dissassemble:
Se presentará entonces esta ventana:
¡Así es mucho más fácil! Un detalle para tomar en cuenta: el THRSim presenta la dirección de destino junto a los saltos (BNE y BRA) pero no inventa ninguna etiqueta. Pan comido, no?
Más de una vez alguien que entendía mucho más que uno nos muestra su programa (el código fuente) diciendo"bla, bla, bla, funciona perfecto, bla, bla", y ante la avalancha de conceptos nos quedamos...
El gran problema es que aun cuando lo ejecutamos -o simulamos- las cosas pasan tan rápido que seguimos igual de perdidos que al principio. Algo parecido ocurre cuando creemos haber resuelto un ejercicio, lo ejecutamos... y nos llevamos la decepción de ver que no hace lo que queríamos. ¿Qué herramienta tenemos para paliar estas situaciones? La ejecución paso a paso. En el simulador THRsim podemos lograrlo de dos maneras:
La primera sería estableciendo puntos de interrupción (breakpoints) en el programa. Para ello cargamos el archivo del código fuente (o guardamos lo que hayamos hecho) y vamos al menú Breakpoint -> Set...
La ventana de diálogo que se presenta nos permitirá indicar en qué punto del programa debe interrumpirse la ejecución.
Atención! Si le indicamos que interrumpa la ejecución en la primer instrucción, no lo hará. En realidad lo tomará como que ya había sido interumpida... así que para que funcione debemos indicar una dirección del PC distinta de la inicial. En el ejemplo el programa inicia en el ORG $C000, por tanto fijaremos un breakpoint en la siguiente instrucción: $C003. Para determinar dónde queremos frenarlo, podemos guiarnos por el ensamblado, al que accedemos con menú File->Assemble (partiendo de la ventana del código fuente). En el ejercicio que voy a usar como ejemplo la ventana de ensamblado se ve así:
El programa que presento como ejemplo informa las unidades, decenas y centenas de un número entero sin signo de ocho bits. Fijé un breakpoint en PC==$C003. Luego iniciamos la ejecución como lo hacemos normalmente (menú Execute -> Run...) y frena en el punto que se muestra en la captura anterior. Notar que en rojo señala el punto en el que frenó la ejecución. En este punto podemos continuar la ejecución hasta el siguiente breakpoint, o ir "paso a paso" con menú Execute->Step (o usando la barra espaceadora o F7). Veamos cómo sería el paso a paso...
Ahora es más sencillo comprender el algoritmo empleado, verificando pacientemente una y otra vez el programa. En el ejemplo el número a desglosar es 123 (en hexadecimal 7B).
La segunda es: ensamblar el programa (con el menú File -> Assemble o con la hot key Ctrl+A) y luego desde esa ventana usar la tecla F7 para ejecutar paso a paso. Si el programa es relativamente corto, este método es más conveniente.
Es posible crear puntos de interrupción para condiciones respecto a acumuladores, registros índice, y posiciones de memoria, entre otros.
A propósito el fuente que usé es el siguiente:
* Dado un numero entero sin signo de 8 bits, informar: * centenas, decenas y unidades en tres posiciones de memoria
ORG $0000 num RMB 1 * el mister en cuestion cen RMB 1 dec RMB 1 uni RMB 1
ORG $C000 CLR cen * arranco poniendo todo en cero CLR dec * porque voy a incrementar cada uno LDAA num rec SUBA #100 * intento restarle 100... BLO mpc * si me pase, no hay mas centenas INC cen * sino, es que habia alguna centena mas BRA rec mpc ADDA #100 * como me pase, le vuelvo a sumar 100 red SUBA #10 * intento restarle 10... BLO mpd * si me pase, no hay mas decenas INC dec * sino, es que habia alguna decena mas BRA red mpd ADDA #10 * recupero los ultimos 10 que le reste STAA uni * quedaron las unidades fin BRA fin
Me despido con el recuerdo del gran Mostaza... tipo sufrido si los hay (para dirigir Racing hay que ser sufrido)... en un gesto propio de alumno que ve cómo se hacia un ejercicio en la clase posterior al examen:
Aprender programación mirando el pizarrón es como aprender cocina mirando la tele... todo muy lindo, pero la verdad está ahí afuera... cuando llega el momento de sentarse frente a la compu y ver si el profesor nos mintió como a niños, ¡ahí se pone complicado! Veamos entonces cómo simular un programa con THRSim.
Partiré de la premisa de que tenemos un programa para probar, en este caso uno bien sencillito: determinar el tipo de triángulo e informarlo con una letra. La validación de si realmente es un triángulo (verificable por las longitudes de los lados) la dejamos fuera.
El programa podría ser así: org $0000 l1 rmb 1 l2 rmb 1 l3 rmb 1 tipot rmb 1 Guardo el ASCII del tipo de triangulo: * I para isosceles; Q para equilatero; E para escaleno
org $c000 ldaa l1 ldab l2 cba beq igual si l1==l2, es equilatero? cmpa l3 caso contrario, todavia puede ser isosceles beq isos cmpb l3 si l2 y l3 son iguales, tambien es isosceles beq isos ldab #'E son todos distintos, es escaleno bra listo igual cmpa l3 si l1==l2 y l1==l3 son iguales... bne isos (sino es isosceles) ldab #'Q ... entonces es equilatero bra listo isos ldab #'I listo stab tipot todos confluyen aqui fin bra fin
En este momento nos centraremos en simular el programa, así que la explicación del algoritmo la dejamos para otro momento. Tampoco es tan complicado... y además puse comentarios para que se pueda seguir.
La primera sección del programa, a partir de la directiva de ORG (origen) en 0000, indica que tenemos cuatro reservas de un byte: los lados del triángulo (l1, l2, l3) y el resultado de la determinación de su tipo (tipoT). Para simular el programa necesitamos primero cargar los valores de los lados. ¿Cómo se hace? Veamos distintas formas.
1. Cargando posiciones de memoria individuales. Menu View -> Memory -> Memory List...
Al acceder a esa opción nos preguntará desde qué dirección de memoria deseamos listar:
Como sugiere la dirección $0000, que es justamente donde definimos la directiva de origen en cuestión, el valor por default es el que buscamos, le damos OK y presenta la memoria:
¿Cómo se completa ahora? Hacemos doble click en el valor actual ($ff indica en este caso un valor no inicializado) y se habilita el cursor para tipear los valores:
Pero... ¿no sería mucho más fácil que la ventana de Memory List nos muestre a qué reserva de memoria (RMB) corresponde cada dirección? Efectivamente, y puede hacerlo. Pero para ello debemos ensamblar el programa. Si solo queremos ensamblarlo, pero no ejecutarlo, vamos al menu "File -> Assemble".
Attenti! Primero hacer foco en la ventana del código fuente. Entonces menú File->Assemble:
Cuando hagamos esto se presentará la ventana LST con el ensamblado... si es que no tenemos errores! Ahora si abrimos nuevamente la ventana Memory List, veremos que se asignaron las etiquetas a las posiciones de memoria:
Esto hace más fácil la carga del juego de prueba.
2. Cargando posiciones de memoria estilo vector
Otra forma de cargar juegos de prueba es editar la memoria en una vista más cercana al concepto de vector, esto es, un valor al lado del otro. Menu View -> Memory -> Memory Dump...
Nuevamente preguntará a partir de qué dirección hacer el volcado (dump), y tal como antes, para este caso nos sirve el valor default ($0000). Se presenta una ventana donde la memoria aparece como renglones de 16 bytes:
Aquí no tenemos las etiquetas de las reservas. Podemos editar la memoria haciendo doble click (tal como antes). La parte derecha de la ventana (que en la captura muestra puntos) es la representación ASCII del valor de memoria. ¿Por qué muestra tantísimos puntos? Porque no todos los códigos ASCII son caracteres imprimibles. Muchos de ellos, particularmente los valores bajos, corresponden a caracteres de control (cosas tales como inicio de archivo, fin de archivo, retorno de línea, etc) que no tienen una representación visual.
3. Inicializando directo en el código
Aunque visualizar la memoria se hace necesario para verificar el resultado de la ejecución del programa, disponemos de una herramienta para cargar los juegos de prueba directamente en el fuente. Lo que haremos es usar la directiva db (define byte) en lugar de RMB (reserve memory byte). Modificamos el fuente:
org $0000 l1 db 5 l2 db 6 l3 db 2 tipot rmb 1 Guardo el ASCII del tipo de triangulo: * I para isosceles; Q para equilatero; E para escaleno
De esta forma estamos incluyendo el juego de prueba directamente en el código. En este caso no representa cambio alguno en la forma de simularlo.
¿Qué diferencia tiene esta forma de simularlo con la carga directa en la memoria?Al usar DB/DW el juego de prueba se carga al momento de ensamblar el fuente. Si deseamos cambiar los valores de prueba, debemos volver a ensamblar. ¡No alcanza con reinicializar el PC y ejecutarlo de nuevo!
Se puede verificar que los valores están en memoria usando las opciones antes descritas (Memory view y memory dump) y en la ventana LST (habiendo ensamblado nuevamente) donde aparece como contenido de la memoria:
Llegado a este punto hemos cargado el juego de prueba. Lo que sigue es ejecutar el programa y verificar si determina correctamente el tipo de triángulo. Para ello desde la ventana de ensamblado (LST) o desde la ventana del fuente ejecutamos el programa :
* menu Execute -> Run * tecla F9 * icono "play" en azul
El programa quedará en el bucle final, hay que detenerlo (el botón STOP, tecla F2 o Menu Execute->Stop). Luego hacemos un Memory Dump... (tal como se explica anteriormente) y podemos verificar que el cuarto byte de la memoria (dirección 0003) tiene el ASCII E, correspondiente a Escaleno:
Repasemos lo visto aquí: * uso de la ventana Memory list * uso de la ventana Memory dump * carga de variables directa en el codigo con define byte (db) * ejecución del programa ¿Preguntas, comentarios?
Mientras practican con el simulador, escuchen El Gran Simulador...