Regístrate | Conectar
El Tamiz Libros Recursos Series Únete 12 Users Online
Skip to content

Computador mágico XXIII – Ordenador C16A III: hola mundo




La serie “El computador mágico” está disponible también en forma de libro.

En el último capítulo de la serie vimos cómo funcionaba el ordenador C16A, las instrucciones que tenía y cómo se implementaba cada una de sus instrucciones, pero solo mostramos un ejemplo de cómo se utilizaban esas instrucciones para hacer cosas más complejas, sin interpretarlo en absoluto… ahora es cuando vamos a interpretar eso.

Para ahorrarte estar yendo y viniendo continuamente a buscar la información a los capítulos anteriores, vamos a incluir aquí tanto el dibujo arquitectural como un resumen del juego de instrucciones:

 

Mnemotécnico ¿Qué hace?
NOP No Operation. Operación que no hace nada.
ST <addr> Almacena (del inglés store) el contenido del acumulador en la posición de memoria <addr>. addr es del inglés address, dirección.
LD <addr> Carga (del inglés load) en el acumulador el contenido de la posición de memoria <addr>.
ADD <addr> Suma (del inglés add) el contenido del acumulador con el contenido de la posición de memoria <addr>, y deja el resultado en el acumulador.
BR <addr> Salta (del inglés branch) la ejecución a la posición <addr>. E decir, en vez de continuar ejecutando en PC+1, continúa ejecutando en la posición de memoria <addr>
BZ <addr> Del inglés branch if zero, salta si es cero. Es decir, lo mismo que BR, pero solo si el contenido del acumulador es un 0.
CLR Limpia (del inglés clear) el acumulador. Es decir, pone un 0 en él.
DEC Decrementa (del inglés decrease) el acumulador y deja el resultado en el propio acumulador.

 

Así que vamos a hacer el programa hola mundo para este ordenador. “hola mundo” es como llaman los programadores al programa más sencillo que se pueda hacer para un determinado entorno, plataforma, framework, sistema o lo que sea. Se le llama así porque generalmente lo que hace ese programa supersencillo es decir “hola” u “hola mundo” o en inglés “hello world”… y nada más. Solo para saber si el sistema funciona.

Nosotros ya pusimos en el último capítulo el código de ese programa:

LD 0x010
ST 0x800
LD 0x011
ST 0x801
LD 0x012
ST 0x802
LD 0x013
ST 0x803
NOP
BR 0x008
NOP
NOP
NOP
NOP
NOP
NOP
0x0048
0x006f
0x006c
0x0061
 

Así que supongamos que ese programa está a partir de la posición 0×000 de memoria. El cómo ha llegado a estar ahí no es importante de momento. Supongamos que un ser humano ha ido poniendo los 0′s y 1′s manualmente en esas posiciones.

Si nunca has programado anteriormente, lo mejor es hacerse un mapa de memoria de qué tiene cada celda (con ciertas abstracciones, esto es ni más ni menos que lo que va haciendo un programador profesional cuando está trabajando, solo que lo hace todo de cabeza y lo hace de forma tan mecánica que casi ni se da cuenta):

0×000 LD 0×010
0×001 ST 0×800
0×002 LD 0×011
0×003 ST 0×801
0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0000
0×801 0×0000
0×802 0×0000
0×803 0×0000
0×804 0×0000

Fíjate en cómo hemos puesto con puntos suspensivos todo el pedazo de memoria que no nos interesa, bien porque no es parte de nuestro programa, bien porque no es parte de la memoria con la que estamos trabajando. Podemos asumir, por ejemplo, que todas esas posiciones de memoria están todo a 0 (recuerda que la operación 000 es la operación NOP, es decir, es como si en ellas hubiera NOP).

También nos resultará muy interesante tener conocimiento de qué valor hay almacenado en el contador de programa y qué valor hay en el acumulador, así que vamos a ponerlos también en nuestra tabla:

PC 0×000 LD 0×010
0×000 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0000 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0000
0×801 0×0000
0×802 0×0000
0×803 0×0000
0×804 0×0000

Y ahora tenemos que ir pensando en qué va haciendo el ordenador en cada ciclo de instrucción. Recuerda que el ciclo era, resumido:

  1. Pido a la memoria la instrucción a la que apunta PC y la decodifico (la interpreto, la entiendo… como quieras verlo).
  2. Ejecuto la instrucción.
  3. Incremento el contador de programa en 1 (salvo que la instrucción del paso 2 me haya ordenado otra cosa).
  4. Vuelvo al paso 1.

Así que empezamos: como PC apunta a 0×000, la instrucción que tengo que ejecutar es LD 0×010. La ejecuto: cargo en el acumulador el contenido de la posición de memoria 0×010 (es decir, un 0×0048). E incremento PC en 1. Así que nuestro mapa de memoria queda así:

PC 0×000 LD 0×010
0×001 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0048 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0000
0×801 0×0000
0×802 0×0000
0×803 0×0000
0×804 0×0000

Fíjate en cómo antes el acumulador era 0×0000 y ahora es 0×0048; el PC antes era 0×000 y ahora es 0×001.

¿Entendido? Pues ahora vuelvo a empezar: la instrucción que ahora me toca ejecutar es la que está en 0×001, que es ST 0×800. Es decir, guardo en la posición 0×800 el contenido del acumulador, que era 0×048. Y luego incremento PC. Es decir, nuestro mapa ahora queda así:

PC 0×000 LD 0×010
0×002 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0048 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0×0000
0×802 0×0000
0×803 0×0000
0×804 0×0000

Fíjate en que la posición 0×800 antes valía 0×0000 y ahora vale 0×0048; el PC antes valía 0×001 y ahora vale 0×002.

Si has entendido esas dos instrucciones, supongo que las instrucciones que van desde la 0×002 a las 0×007 las tienes fáciles, ¿verdad? Hacen lo mismo pero con orígenes y destinos consecutivos. ¿Te parece bien si no las explicamos? Bien, nos imaginamos que ya hemos ejecutado otras 6 instrucciones y entonces nuestro mapa ha quedado de la siguiente forma:

PC 0×000 LD 0×010
0×008 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0061 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0x006f
0×802 0x006c
0×803 0×0061
0×804 0×0000

Date cuenta de que, como hemos ejecutado otras 6 instrucciones, PC ha ido conteniendo 0×003, 0×004… hasta 0×008. Aunque no es importante a partir de ahora, el ACC habrá quedado con 0×0061, que fue lo último que le cargaron (en la instrucción 0×007: LD 0×013).

Vale, pues sigamos. Ahora PC apunta a la posición 0×008, que contiene NOP. Bueno, pues ejecuto NOP  (esta es sencilla: simplemente no hago nada) e incremento PC, quedando así:

PC 0×000 LD 0×010
0×009 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0061 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0x006f
0×802 0x006c
0×803 0×0061
0×804 0×0000

Ahora ejecutamos la instrucción que hay en 0×009, BR 0×008. Es decir, en vez de dejar que PC se incremente en 1, meto 0×008 en PC. Por lo tanto, queda así:

PC 0×000 LD 0×010
0×008 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0061 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0x006f
0×802 0x006c
0×803 0×0061
0×804 0×0000

Ejecuto la posición 0×008, que es NOP, y queda así:

PC 0×000 LD 0×010
0×009 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0061 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0x006f
0×802 0x006c
0×803 0×0061
0×804 0×0000

Ejecuto la posición 0×009, que es BR 0×008, y queda así:

PC 0×000 LD 0×010
0×008 0×001 ST 0×800
0×002 LD 0×011
ACC 0×003 ST 0×801
0×0061 0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0048
0×801 0x006f
0×802 0x006c
0×803 0×0061
0×804 0×0000

En resumen, espero que te hayas dado cuenta de que hemos entrado en un bucle: ejecuto NOP, ejecuto BR 0×008, ejecuto NOP, ejecuto BR 0×008, NOP, BR 0×008, NOP, BR 0×008… En cierto modo podemos decir que nuestro programa “ha acabado ahí”. Bueno, no es que haya acabado, porque el ordenador no acaba, sigue ejecutando eso para siempre (hasta que alguien apague su interruptor, claro), pero nuestro programa ya ha acabado.

¿Y qué ha hecho nuestro programa? Fíjate en que en las posiciones 0×800 a 0×803 hay cosas que antes no estaban. ¿No te suenan esas posiciones? ¡Son las que correspondían a la pantalla! Es decir, si por un casual los valores que hay en esas posiciones corresponden con el código ASCII de alguna letra, la pantalla las dibujará en sus posiciones, ¿no? Échale un vistazo, si quieres, a la codificación ASCII que vimos en el capítulo de representación textual, y verás que 0×48 es el valor ASCII de la “H”, 0x6f es el de la “o” y las 0x6c y 0×61 son respectivamente la “l” y la “a”. Es decir, que nuestra pantalla habrá quedado así:

 

¡Pero… qué bonito! Nuestro ordenador dice “Hola”.

Si has entendido esto, ya tienes una ligera idea de lo que significa programar: poner esas instrucciones de forma astuta para que el ordenador haga lo que queremos que haga.

Pero antes de seguir… ¿te ha resultado complicado apañarte con tantos 0xcosa y LD 0xabc y cosas así? Si te ha resultado complicado, es normal. Por eso, los ingenieros de computadoras, que son muy vagos,[1] inventaron notaciones para hacer esto un poco más sencillo. Vamos a proponer nosotros una, sencilla, pero suficiente para lo que vamos a ver a partir de aquí.

Partimos de nuestro programa tal y como lo hemos visto:

0×000 LD 0×010
0×001 ST 0×800
0×002 LD 0×011
0×003 ST 0×801
0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
0×010 0×0048
0×011 0x006f
0×012 0x006c
0×013 0×0061
0×800 0×0000
0×801 0×0000
0×802 0×0000
0×803 0×0000
0×804 0×0000

Y ahora vamos a aplicarle algunas simplificaciones de notación. Para empezar, las direcciones que nos sean especialmente importantes vamos a ponerlas con una etiqueta que nos resulte cómoda de recordar. Por ejemplo, vamos a llamar /H al lugar donde está la “H” inicialmente (es decir en 0×010). Y lo equivalente, no solo con las demás letras de nuestro saludo, sino también con las posiciones de la pantalla. Así que nos quedaría así:

0×000 LD 0×010
0×001 ST 0×800
0×002 LD 0×011
0×003 ST 0×801
0×004 LD 0×012
0×005 ST 0×802
0×006 LD 0×013
0×007 ST 0×803
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
/H 0×0048
/O 0x006f
/L 0x006c
/A 0×0061
/PANT1 0×0000
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

Pero… bueno… ya que las tenemos así, si no las usamos en nuestro mnemotécnico, no sirve de nada, así que lo cambiamos por:

0×000 LD /H
0×001 ST /PANT1
0×002 LD /O
0×003 ST /PANT2
0×004 LD /L
0×005 ST /PANT3
0×006 LD /A
0×007 ST /PANT4
0×008 NOP
0×009 BR 0×008
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
/H 0×0048
/O 0x006f
/L 0x006c
/A 0×0061
/PANT1 0×0000
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

Hombre, ya que estamos, podemos hacer lo mismo con la dirección que usaremos para el BR de la 0×009, ¿no?

0×000 LD /H
0×001 ST /PANT1
0×002 LD /O
0×003 ST /PANT2
0×004 LD /L
0×005 ST /PANT3
0×006 LD /A
0×007 ST /PANT4
/HALT NOP
0×009 BR /HALT
0x00A NOP
0x00B NOP
0x00C NOP
0x00D NOP
0x00E NOP
0x00F NOP
/H 0×0048
/O 0x006f
/L 0x006c
/A 0×0061
/PANT1 0×0000
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

Y las demás direcciones… pues hombre… no nos sirven de mucho, ¿no? Quiero decir que, a menos que vayamos a saltar a ellas o algo así… en fin… ya sabemos que son consecutivas, y que el PC se va a ir incrementando de 1 en 1… no nos aportan mucho. Vale, pues las quitamos. Bueno, no todas: vamos a dejar algunas, por ejemplo de 16 en 16. Pero solo por referencia, para que no nos perdamos del todo, ¿vale?

0×000 LD /H
ST /PANT1
LD /O
ST /PANT2
LD /L
ST /PANT3
LD /A
ST /PANT4
/HALT NOP
BR /HALT
NOP
NOP
NOP
NOP
NOP
NOP
/H 0×010 0×0048
/O 0x006f
/L 0x006c
/A 0×0061
/PANT1 0×800 0×0000
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

¿Alguna cosa más? Sí, también podemos hacer lo siguiente: lo que para nosotros tenga semántica de carácter ASCII, lo escribimos en ASCII entrecomillado, en lugar de expresar su valor hexadecimal. En nuestro caso, las letras de “Hola”:

0×000 LD /H
ST /PANT1
LD /O
ST /PANT2
LD /L
ST /PANT3
LD /A
ST /PANT4
/HALT NOP
BR /HALT
NOP
NOP
NOP
NOP
NOP
NOP
/H 0×010 ‘H’
/O ‘o’
/L ‘l’
/A ‘a’
/PANT1 0×800 0×0000
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

¿Ya? No, nos queda una cosa más. A menudo es muy habitual añadir algunas notas en el programa. Son notas que el sistema debe ignorar, pero ayudan al ser humano a entenderlo. Es lo que los programadores llaman “comentarios”. Vamos a representarlo con una almohadilla: todo lo que haya desde una almohadilla hasta el final de la línea simplemente debe ignorarse. Está ahí solamente como nota para el lector humano. Por ejemplo:

0×000 LD /H # Copiamos las letras de la memoria a la pantalla
ST /PANT1
LD /O
ST /PANT2
LD /L
ST /PANT3
LD /A
ST /PANT4
/HALT NOP # Bucle infinito
BR /HALT
NOP
NOP
NOP
NOP
NOP
NOP
/H 0×010 ‘H’  # Zona de datos
/O ‘o’
/L ‘l’
/A ‘a’
/PANT1 0×800 0×0000  # Zona de I/O
/PANT2 0×0000
/PANT3 0×0000
/PANT4 0×0000
0×804 0×0000

Ahora sí. Esto ya es más agradable de leer. Esto es lo que se llama un lenguaje ensamblador: es una transcripción directa, o casi directa, de las instrucciones de la máquina, quizá con pequeños cambios de notación para hacerlo más entendible por el ser humano… pero solo eso. Más adelante veremos los lenguajes de programación, que darán un nivel de abstracción más, pero aquí no queremos definir un lenguaje de programación (aunque lo parezca), sino solo un cambio de notación para que el ser humano entienda el programa sin demasiados problemas.

En el próximo capítulo veremos unos cuantos ejemplo de programas, un poco más complejos, para ver la potencia de esta aproximación.

 

 

 

 

  1. En serio: el tipo que inventó el acueducto es porque era un vago que no quería ir a por agua al río; el que inventó la lavadora es porque estaba harto de tener que lavar la ropa a mano; el que inventó el motor de explosión es porque estaba cansado de cuidar de los caballos; y así con todo… bueno, más o menos. []

Sobre el autor:

J ( )

 

{ 6 } Comentarios

  1. Gravatar Brigo | 07/10/2013 at 10:53 | Permalink

    Muy bien explicado. Dos dudas: ¿por qué tan estrecho? ¿no sería mejor, por ejemplo que ST y /PANT2 esté en la misma línea?: LD /O ST /PANT2 Y así.

    La otra ¿no se puede hacer el salto a la propia dirección del salto? Br 0×009

  2. Gravatar J | 07/10/2013 at 01:26 | Permalink

    Brigo,

    ¿no sería mejor, por ejemplo que ST y /PANT2 esté en la misma línea?: LD /O ST /PANT2 Y así.

    Bueno, esos son los pasos que vas dando para convertir este ensamblador en un lenguaje de programación más serio. Por ejempo, lo que tú estas diciendo, ¿no te gustaría más que fuera $PANT2=$O? Date cuenta de cómo se va acercando…

    La otra ¿no se puede hacer el salto a la propia dirección del salto? Br 0×009

    Se puede. Pero ten en cuenta que el NOP se usa para saber que la CPU está ociosa (cuantos más NOPs haya, más ociosa está), así que se usa para gestión de energía. Revisa el capítulo anterior.

  3. Gravatar Brigo | 07/10/2013 at 07:55 | Permalink

    No me he expresado correctamente: A mi el LD /O me sale en una sola línea pero el ST /PANT2 me sale en dos líneas, lo que me parece poco prolijo. A eso me refiero con todo en la misma línea.

    Por cierto, viendo esto me están dando ganas de programar probar a programar en ensamblador en el pc, ¿Sería mucho más difícil? :-)

  4. Gravatar J | 08/10/2013 at 06:17 | Permalink

    Ah, entiendo. Pues chico, no sé… será cosa del navegador, yo lo veo en la misma línea. ¿En cuál de los listados te ocurre?

    Hoy en día (casi) nadie programa ensamblador para PC. Los PCs son muy complejos, y los compiladores son muy listos. Tanto que, para la mayor parte de los mortales, el código ensamblador que genera el compilador a partir de tu código en C es más eficiente que el que podrías generar tú directamente (a menos que seas realmente bueno programando ensamblador, supongo). Eso por no hablar de que eres muchos órdenes de magnitud más productivo.

    Supongo que en el sector de los microcontroladores todavía se programará directamente en ensamblador, aunque con tanto Arduino igual eso cada vez es menos cierto.

  5. Gravatar Brigo | 10/10/2013 at 01:07 | Permalink

    Me ocurre en todas las tablas, podría ser del navegador, si. He probado a modificar el tamaño de la fuente, pero sigue igual.

    Lo de programar en c. máquina no lo digo porque sea eficaz o no, si no por curiosidad (además fardaría bastante decir que estoy programando en código máquina), la duda es si hay que hacerlo “a pelo” como en tu máquina o si existen algunas herramientas de ayuda. Porque me da a mi que la memoria de vídeo, por ejemplo no ha de ser tan sencilla de utilizar como la tuya, por ejemplo. :-)

  6. Gravatar J | 10/10/2013 at 06:36 | Permalink

    Si es por molar y por aprender, yo empezaría desde abajo. Busca información sobre el ordenador “simplez” (en el cual está basado este diseño) y busca emuladores y entornos para él. No sé ahora, pero en mi época se usaba mucho en la universidad para enseñar estas cosas precisamente.

    Luego puedes pasar a procesadores de verdad. Cuando yo estudiaba estas cosas, estaban de moda los 88HCxx de Motorola y los Atmel (basados en intel). Son procesadores más serios, en los que ya tienes que saber de interrupciones, pilas,… (son cosas que veremos por encima en capítulos posteriores de esta serie). Y además es que incluso en esos cacharros, al final la gente programa en C (mira por ejemplo el IDE de atmel: http://www.atmel.com/microsite/atmel_studio6/)

Escribe un comentario

Tu dirección de correo no es mostrada. Los campos requeridos están marcados *

Al escribir un comentario aquí nos otorgas el permiso irrevocable de reproducir tus palabras y tu nombre/sitio web como atribución.