Unidad 1. Arquitectura del computador

Introducción

En esta unidad vas a aprender los bloques de construcción básicos del hardware de un sistema de cómputo moderno, cómo esos bloques pueden combinarse para construir computadores y cómo hacen para ejecutar programas.

Propósito de aprendizaje

Comprender cómo funciona el hardware de un computador moderno desde una perspectiva sistémica, es decir, estudiando las partes que lo componen y cómo se conectan para conseguir funciones más complejas.

Te acercarás al lenguaje de programación ensamblador y verás la relación de este con el lenguaje C.

Temas

  • Organización básica de un computador digital moderno.

  • Lenguaje de máquina-lenguaje ensamblador-lenguaje C

Evaluación

Enunciado

Debes hacer una aplicación en el lenguaje ensamblador estudiado en esta unidad. La aplicación deberá funcionar en un loop infinito. La aplicación lee el teclado y pinta en la pantalla un cuadrado si presionas la letra c. Si presionas la letra b debe borrar la pantalla.

Consideraciones

Advertencia

LA CREACIÓN DE EQUIPOS

Habla con el profesor para crear el equipo de trabajo.

  • Aquí está el enlace de la evaluación.

  • Debes crear un equipo de trabajo al cual se unirán todos los miembros. El equipo solo lo crea un integrante y los demás miembros solo deben unirse.

  • Al repositorio solo debes subir dos archivos. El archivo README.md y un archivo program.asm con la solución. Ten presente que puedes hacer pruebas en windows utilizando las herramientas gráficas.

  • En la documentación del repositorio debes explicar cómo resolviste el problema.

  • Una vez termines debes validar la solución con el profesor.

Trayecto de actividades

Estos ejercicios te servirán para preparar la evaluación de esta unidad. Realiza cada uno con detenimiento y consciencia. No dejes de preguntar si tienes dudas.

Ejercicios

Ejercicio 1: Demo

Lo primero que harás es utilizar un simulador en el cual te he construido un computador. Lo puedes descargar aquí. En los compu de la U está instalada la máquina virtual de java. Es posible que en tu computador personal no la tengas. En ese caso ve a la sección ¿Qué herramientas necesitas?

Una vez descargues el archivo .zip, lo debes descomprimir. Busca el archivo Digital.exe y ejecútalo.

Ve a la opción Edit/Settings/Advanced y en Java library adiciona esta biblioteca que está en Digital/custom/digitaln2t-1.0-SNAPSHOT.jar.

Ahora abre el archivo demo que está en Digital/custom/project05/ComputerFull4Key.dig.

Debes ver algo así:

HackComputer demo

Por ahora nota que hay tres bloques de interés: ROM (Program), CPU y Memory. Dale click derecho al bloque que dice ROM, selecciona Advance y en la opción File carga el archivo Digital/custom/project05/fill.hex. Las primeras líneas del archivo fill.hex se ven así:

:020000000040BE
:0200020010EC00
:020004001000EA
:0200060008E30D
:02000800006096
:02000A0010FCE8
:02000C001300DF
:02000E0005E308

En esas líneas están almacenados los códigos de máquina de las instrucciones que ejecutará la CPU.

En este punto ya tienes configurado el DEMO. Se trata de un computador de 16 bits que ejecutará un programa almacenado en la memoria ROM. Para ejecutar el programa selecciona Simulation/Start of Simulation. Nota el ícono de este comando. Puedes iniciar la simulación usando el botón marcado con el mismo ícono. Al iniciar la simulación se activa el botón que la detiene (botón con el cuadrado de color rojo).

Un vez inicie la simulación presiona con el mouse una de las teclas marcadas con los números 1, 2, 3 o 4. Se activará una ventana titulada HACK Display. Esta ventana simula una pantalla de 512 pixels de ancho por 256 pixels de alto. Mueve la ventana hacia la izquierda en caso de que te impida el acceso a las teclas. Deja presionada cualquiera de las teclas. Verás que los pixels del Hack Display se encenderán de color negro. Si dejas de presionar la tecla los pixels cambiarán a blanco.

Truco

CON ESTE COMPUTADOR PODEMOS HACER JUEGOS

El computador que acabas de simular tiene todos los elementos para ejecutar juegos. Tiene una memoria (ROM) para almacenar las instrucciones y datos iniciales del juego. Tiene un circuito para ejecutar las instrucciones (CPU). Tiene una memoria para ir almacenando cómo va cambiando el juego (Memory). Tiene teclado y pantalla (periféricos de entrada salida).

Si ya terminaste de experimentar, cierra la ventana HACK Display y termina la simulación.

Ejercicio 2: concepto de programa almacenado

Ahora te mostraré el programa que está ejecutando el computador. Este programa está ALMACENADO en la memoria de programa (ROM). Puedes pensar la memoria como un arreglo de n posiciones donde cada posición tiene un índice o dirección que va desde la posición 0 para el primer componente del arreglo hasta la posición n-1 para el último. En cada posición se almacena un código de máquina que la CPU LEERÁ (leer), DECODIFICARÁ (entender) y EJECUTARÁ (realizar la operación solicitada):

Dirección

Código de máquina

0

0100000000000000

1

1110110000010000

2

0000000000010000

3

1110001100001000

4

0110000000000000

5

1111110000010000

6

0000000000010011

7

1110001100000101

8

0000000000010000

9

1111110000010000

10

0100000000000000

11

1110010011010000

12

0000000000000100

13

1110001100000110

14

0000000000010000

15

1111110010101000

16

1110101010001000

17

0000000000000100

18

1110101010000111

19

0000000000010000

20

1111110000010000

21

0110000000000000

22

1110010011010000

23

0000000000000100

24

1110001100000011

25

0000000000010000

26

1111110000100000

27

1110111010001000

28

0000000000010000

29

1111110111001000

30

0000000000000100

31

1110101010000111

Muy claro el programa, ¿Verdad? :)

Estas cadenas de unos y ceros no resultan fáciles de entender para las personas. Afortunadamente, cada cadena puede representarse de manera simbólica así:

Dirección

Código ensamblador

0

@16384

1

D=A

2

@16

3

M=D

4

@24576

5

D=M

6

@19

7

D;JNE

8

@16

9

D=M

10

@16384

11

D=D-A

12

@4

13

D;JLE

14

@16

15

AM=M-1

16

M=0

17

@4

18

0;JMP

19

@16

20

D=M

21

@24576

22

D=D-A

23

@4

24

D;JGE

25

@16

26

A=M

27

M=-1

28

@16

29

M=M+1

30

@4

31

0;JMP

Al lenguaje anterior se le conoce como lenguaje ensamblador y tiene una correspondencia uno a uno con el lenguaje de máquina. Al proceso de convertir el programa de lenguaje ensamblador a lenguaje de máquina se le conoce como ENSAMBLADO.

¿Ahora si es más claro qué hace el programa? Puede que no. El lenguaje ensamblador es más fácil de leer y escribir que el lenguaje de máquina, pero sigue siendo un reto para las personas escribir programas a ese nivel. Adicionalmente, ten presente que el lenguaje de máquina y el lenguaje ensamblador son PARTICULARES para cada CPU. Eso quiere decir que tendrás que aprender tantos lenguajes ensamblador como CPU donde quieres que se ejecute tu programa.

¿Hay alguna manera de escribir un programa en un único lenguaje? Si. Mediante los lenguajes de alto nivel. Tu ya conoces uno, C#. Si programas en un lenguaje de alto nivel puedes generar el lenguaje en ensamblador mediante un proceso conocido como COMPILACIÓN. Este proceso es realizado por una herramienta particular para cada CPU llamada COMPILADOR.

Ahora te voy a mostrar una nueva versión del programa, pero esta vez en un lenguaje de alto nivel conocido como C++:

MEMORY[16] = 16384;

while (true)
{
    if (MEMORY[KEYBOARD] == 0)
    {
        if ((MEMORY[16] - 16384) > 0)
        {
            MEMORY[16] = MEMORY[16] - 1;
            MEMORY[MEMORY[16]] = 0x0000;
        }
    }
    else
    {
        if ((MEMORY[16] - 24576) < 0)
        {
            MEMORY[MEMORY[16]] = 0xFFFF;
            MEMORY[16] = MEMORY[16] + 1;
        }
    }
}

Nota

RETO

¿Te animas ahora si a decir qué hace el programa? Trata de analizarlo, pero no te preocupes porque en un momento lo discutiremos.

Si te gusta la historia, te voy a dejar aquí un documental muy corto sobre la invención del programa almacenado.

Ejercicio 3: concepto de variable

El arreglo MEMORY representa al circuito Memory que te mostré previamente en el diagrama del computador. MEMORY[16] representa el contenido de la posición de memoria 16, es decir, MEMORY[16] es una VARIABLE. Entonces una variable no es más que la representación del contenido de una posición de memoria, en este caso, la posición 16. Ten presente que en los programas que has usado hasta ahora no has tenido que indicar de manera explícita la dirección o posición de la variable, sino que usas un NOMBRE, el nombre de la variable para representar el contenido de esa posición de memoria.

Nota

PARA REFLEXIONAR

¿Cuando usas el nombre de la variable en un programa te refieres a su dirección o a su contenido? Por ejemplo:

variable1 = variable2 + 1;

¿Qué está pasando en la expresión anterior? Piensa en direcciones y contenido cuando analices la pregunta.

Ejercicio 4: concepto de entrada-salida mapeada a memoria

La variable MEMORY[KEYBOARD] es especial. En esa posición se almacena un cero si no hay una tecla presionada o un valor diferente de cero que indica cuál tecla se presionó. ¿Notas que leer si hay una tecla presionada o no es como leer una variable? a esto se le conoce como entrada-salida mapeada a memoria. ¿Cómo aparece el valor en memoria? Primero observa que al bloque Memory se conectan cuatro teclas marcadas como 1, 2, 3 y 4. Internamente, Memory tiene un circuito que se encarga de determinar qué tecla está presionada y luego almacena un número que la representa en la posición de memoria que denota KEYBOARD que será la dirección 24576 para este computador.

El programa también está haciendo una operación de salida. Está pintando en pantalla. El truco es el mismo (entrada-salida mapeada a memoria). La pantalla del computador tiene 256x512 pixeles, es decir, 131072 pixeles. Esos pixeles están asociados a ciertas posiciones de memoria. En este caso desde la dirección 16384 hasta la 24575. De igual manera que en el caso del teclado, en Memory hay un circuito que lee las posiciones de memoria anteriores y pinta en la pantalla la información que allí se encuentra.

Nota esta línea:

MEMORY[MEMORY[16]] = 0xFFFF;;

Supón que en MEMORY[16] está almacenado el valor 16384, es decir, la dirección inicial del rango de posiciones que representan a la pantalla. Por tanto, se puede transformar la línea anterior a:

MEMORY[16384] = 0xFFFF;

¿Qué significa 0xFFFF? Es un número en base 16. A diferencia del sistema base 10 que usamos todo el tiempo, el sistema base 16 tiene 16 símbolos para representar cantidades numéricas. Tiene los mismos 10 símbolos del sistema base 10 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) y 6 más (A, B,C, D, E, F). En este caso, el número 0xFFFF indica que todos los pixeles representados en la posición 16384 deben encenderse. Nota también la línea:

MEMORY[MEMORY[16]] = 0x0000;

Si MEMORY[16] tiene almacenado el valor 16384 entonces se está indicando lo contrario, es decir, los pixeles representados por esa posición de memoria se deben apagar.

Nota

RETO (continuación)

¿Te animas ahora a explicar el programa?

Ejercicio 5: fetch-decode-execute

¿Cómo funciona una CPU? En términos generales una CPU hace tres cosas: fetch o buscar una instrucción. Las instrucciones están guardadas, en nuestro computador de ejemplo, en la memoria de programa. Luego la CPU decodifica la instrucción, es decir, determina qué operación debe realizar. Finalmente, la ejecuta, es decir, realiza la operación decodificada.

Regresemos a nuestro programa y analicemos detenimiento cómo se ejecuta. ¿En qué dirección de la memoria de programa inicia la ejecución? La CPU inicia a buscar instrucciones en la dirección 0 de la memoria de programa:

la CPU inicia

Nota en la figura la salida PC del circuito de la CPU. Esta salida se conecta a la entrada A de la memoria ROM. La entrada A es la entrada de dirección de la memoria. En respuesta, la memoria ROM muestra en su salida D el contenido de la posición presentada en A. En este caso el número en D es el 0x4000. Quiere decir que en la posición 0 de la memoria ROM está almacenado el número 0x4000. Si cambias el contenido de la posición 0 estarás cambiando la primera instrucción del programa. La instrucción 0x4000 es leída por la CPU por medio de la entrada instruction y comienza el proceso de decodificación.

¿Qué hace la instrucción 0x4000? Para entender mejor la instrucción te voy a mostrar cómo convertir el número 0x4000 a su representación binaria.

Ejercicio 6: números en base 2

La representación binaria no es más que la representación en base 2. Ten presente que así como en base 10 hay 10 símbolos, en base 16 hay 16 símbolos, en base 2 solo tendrás dos símbolos: 0 y 1. Mira con detenimiento la siguiente tabla que te muestra lo mismos números representados en las tres bases:

Base 10

Base 16

Base 2

0

0

0000

1

1

0001

2

2

0010

3

3

0011

4

4

0100

5

5

0101

6

6

0110

7

7

0111

8

8

1000

9

9

1001

10

A

1010

11

B

1011

12

C

1100

13

D

1101

14

E

1110

15

F

1111

Convertir de base 16 a base 2 es muy fácil. Solo tienes que representar cada símbolo en base 16 por su equivalente en base 2. Así el símbolo 4 en base 16 se representa por medio de 3 bits: 100. Te estarás preguntando ¿Qué es un bit? ¿Por qué 3 bits si en la tabla me muestras 4 bits? Un bit no es más que un símbolo de la representación en binario. En la cadena 100 el bit más significativo, el que está más a la izquierda, es 1 y el menos significativo, el que está más a la derecha, es 0. Así como en base 10, los ceros a la izquierda no modifican la representación del número. Por tanto 0100 es equivalente a 100. Regresando a la cuestión inicial, observa:

Base 16

Base 2

0x4000

0100 0000 0000 0000

Qué tal si tu mismo lo intentas:

Base 16

Base 2

0x1234

0001 0010 0011 0100

0xF0A2

1111 0000 1010 0010

0xFFFF

?

0x0C0D

?

0x40B0

?

Ejercicio 7: instrucciones tipo A

Las instrucciones de la CPU que estamos analizando tienen 16 bits. La CPU cuenta únicamente con dos tipos de instrucciones. Las instrucciones tipo A y las instrucciones tipo C. Las instrucciones tipo A tienen, TODAS, el bit de mayor peso siempre en 0. Por su parte, las instrucciones tipo C tienen los tres bits de mayor peso SIEMPRE en 111.

¿De qué tipo es la instrucción 0x4000? Convierte de nuevo a base 2 el número. Fíjate en los bits de mayor peso. ¿Te diste cuenta? se trata de una instrucción tipo A porque el bit de mayor peso está en 0. ¿Qué hacen las instrucciones tipo A? Estas instrucciones SIEMPRE hacen lo mismo: almacenan en el circuito de la CPU los 15 bits menos significativos de la instrucción. ¿En dónde se almacenan esos bits? en una memoria interna de la CPU llamada REGISTRO A.

En resumen. La instrucción tipo A 0x4000 al ejecutarse hace que la CPU almacene el número 0x4000 en el REGISTRO A. Pero profe, me habías dicho que solo almacenaba los 15 bits de menor peso de la instrucción y ahora me muestras 16 bits (0x4000). No te preocupes, lo que pasa es que se adiciona un bits más, a la izquierda, pero ese bit es 0. Tu me dirás entonces si esto cambia el número o no.

Ejercicio 8: conversión de base 2 a base 10

¿Puedes regresar unos ejercicios antes y ver la representación del programa en lenguaje ensamblador? ¿Cuál sería la representación simbólica de la instrucción 0x4000 en lenguaje ensamblador? ¡Excelente! En efecto es @16384. Y como sabemos que es una instrucción tipo A podemos decir que la CPU está cargando en el registro A el número 16384. ¿Qué? ¿Te perdiste? Tranquilo vas bien. Si pensaste que el valor cargado en el registro A era 0x4000 déjame decirte que estás entendiendo. Lo que pasa es que 0x4000 es el número 16384 en base 10. ¿Cómo se convierte? Mira, es muy fácil:

  • Escribe 0x4000 en base 2: 0100 0000 0000 0000

  • Cada uno de los bits tiene un peso. El bit menos significativo (\(b_0\)) tiene un peso de \(2^{0}\) el siguiente hacia la izquierda (\(b_1\)) \(2^{1}\) y así sucesivamente hasta la posición 16 (\(b_{15}\)) que tendrá un peso de \(2^{15}\).

  • Puedes calcular el valor en base 10 así:

    \[\sum_{i=0}^{15} 2^i*b_i = 2^{14}*1 = 16384\]

Ejercicio 9: instrucciones tipo C

Luego de buscar la instrucción en la posición 0 de la ROM, decodificarla y ejecutarla ¿Qué sigue? La verdad es muy simple la respuesta ¿Quieres intentarlo? La CPU busca ahora en la dirección 1 de la ROM y así seguirá a menos que una de las instrucciones que ejecute le indique que debe buscar en otra posición de la memoria ROM.

Observa la siguiente figura:

la CPU inicia

¿Qué valor tiene la instrucción almacenada en la posición 1 de la memoria ROM?

Tienes razón es 0xEC10. La convertimos juntos a base 2:

Base 16

Base 2

0xEC10

1110 1100 0001 0000

¿Qué tipo de instrucción es? ¿Será tipo A? No porque el bit de mayor peso no es 0. ¿Será tipo C? Si porque los tres bits de mayor peso son 1. ¡Genial! ¿Qué hace esta instrucción? Esta respuesta es un pelín (pero solo un pelín) más complicada. Resulta que las instrucciones tipo C pueden hacer MUCHAS cosas. Observa de nuevo el programa en lenguaje ensamblador. Dime cuál es la representación en ese lenguaje de la instrucción 0xEC10. ¿Ya lo tienes? Muy bien, la representación es D=A. En general una instrucción tipo C se represente en lenguaje ensamblador así:

destino=operación;salto

Los campos destino y salto son opcionales. Si los omites entonces debes hacer lo mismo con los símbolos = y ; respectivamente. En D=A el destino será C y la operación es A. Te explico. D es otro REGISTRO de la CPU. Llevamos tres. El registro A, el registro D y el registro PC. ¿PC? Si, en este registro se almacena la dirección de la instrucción que se está ejecutando. Por tanto D=A almacena en el registro D el contenido del registro A. Como te comenté, las instrucciones tipo C codifican MUCHAS funciones. Cada uno de los 16 bits de la instrucción tipo C sirve para indicar qué debe hacer la CPU. Observa:

1 1 1 a c1 c2 c3 c4 c5 c6 d1 d2 d3 j1 j2 j3

  • Los tres bit de mayor peso siempre en 1 indicando que es una instrucción tipo C.

  • a c1 c2 c3 c4 c5 c6 indican la operación que debe realizar la CPU.

  • d1 d2 d3 indican el destino o el resultado de la operación.

  • j1 j2 j3 indican si debe ocurrir un salto, es decir, si el registro PC debe modificarse con un valor particular para saltar a una posición específica de la memoria de programa.

¿Cuáles son las posibles operaciones?

Nota

¿Qué es Memoria[A] en tabla?

Se refiere al contenido de la posición de memoria cuya dirección está almacenada en el registro A.

a c1 c2 c3 c4 c5 c6

Operación

0 1 0 1 0 1 0

Produce un 0

0 1 1 1 1 1 1

Produce un 1

0 1 1 1 0 1 0

Produce un -1

0 0 0 1 1 0 0

Lee el valor del registro D

0 1 1 0 0 0 0

Lee el valor del registro A

0 0 0 1 1 0 1

Invierte todos los bits del registro D

0 1 1 0 0 0 1

Invierte todos los bits del registro A

0 0 0 1 1 1 1

Realiza el complemento a 2 del registro D: -D

0 1 1 0 0 1 1

Realiza el complemento a 2 del registro A: -A

0 0 1 1 1 1 1

Realiza D+1

0 1 1 0 1 1 1

Realiza A+1

0 0 0 1 1 1 0

Realiza D-1

0 1 1 0 0 1 0

Realiza A-1

0 0 0 0 0 1 0

Realiza D+A

0 0 1 0 0 1 1

Realiza D-A

0 0 0 0 1 1 1

Realiza A-D

0 0 0 0 0 0 0

Realiza D and A bit a bit

0 0 1 0 1 0 1

Realiza D or A bit a bit

1 1 1 0 0 0 0

Lee el valor Memoria[A]

1 1 1 0 0 0 1

Invierte todos los bits de Memoria[A]

1 1 1 0 0 1 1

Realiza el complemento a 2 de Memoria[A]: -Memoria[A]

1 1 1 0 1 1 1

Realiza Memoria[A] + 1

1 1 1 0 0 1 0

Realiza Memoria[A] - 1

1 0 0 0 0 1 0

Realiza D+Memoria[A]

1 0 1 0 0 1 1

Realiza D-Memoria[A]

1 0 0 0 1 1 1

Realiza Memoria[A]-D

1 0 0 0 0 0 0

Realiza D and Memoria[A] bit a bit

1 0 1 0 1 0 1

Realiza D or Memoria[A] bit a bit

¿Cuáles son las posibles destinos?

d1 d2 d3

Destino

0 0 0

Ninguno

0 0 1

Almacena en Memoria[A]

0 1 0

Almacena en D

0 1 1

Almacena en Memoria[A] y en D

1 0 0

Almacena en A

1 0 1

Almacena en A y en Memoria[A]

1 1 0

Almacena en A y en D

1 1 1

Almacena en A, en Memoria[A] y en D

Y ¿Cuáles son las posibles saltos?

Aquí tengo que explicarte antes varias cosas. Las instrucciones tipo C con saltos modifican el PC. Estas instrucciones son MUY, MUY importantes porque permiten modificar el FLUJO DEL PROGRAMA. ¿Para que sirve modificar el flujo del programa? Pues nada más ni nada menos que para implementar estructuras de control como los IF, los WHILE, los FOR, etc. Estas instrucciones dependen de las operaciones que realiza la CPU, basado en esas operaciones se decide si el salto se realiza o no. ¿Hacia que dirección de memoria ROM se salta? Tu lo decides previamente almacenando en el registro A el valor de la dirección.

j1 j2 j3

Mnemotécnico

Efecto

0 0 0

No modifica el PC

0 0 1

JGT

Si operación > 0 entonces PC = A

0 1 0

JEQ

Si operación == 0 entonces PC = A

0 1 1

JGE

Si operación >= 0 entonces PC = A

1 0 0

JLT

Si operación < 0 entonces PC = A

1 0 1

JNE

Si operación != 0 entonces PC = A

1 1 0

JLE

Si operación <= 0 entonces PC = A

1 1 1

JMP

No hay condición PC=A

Nota

RETO

Con toda la información anterior te animas a analizar la instrucción D=A

Nota

ALERTA DE SPOILER

Ahora analicemos juntos para que repases

La instrucción D=A tiene la representación en hexadecimal y en binario así:

Base 16

Base 2

0xEC10

1110 1100 0001 0000

Analizando de izquierda a derecha:

  • 111: instrucción tipo C

  • 0 1100 00: a c1 c2 c3 c4 c5 c6. Esta combinación indica que la operación es leer el registro A.

  • 01 0: d1 d2 d3. Almacenar en el registro D.

  • 000: no modifica el PC, no hace salto. La próxima instrucción la tomará de PC = PC + 1.

Ejercicio 10: escribir una variable

Continuamos analizando el programa:

Dirección

Código ensamblador

0

@16384

1

D=A

2

@16

3

M=D

Las instrucciones @16 y M=D permiten almacenar en una variable ubicada en la dirección de memoria 16 el valor 16384. Observa la siguiente figura:

CPU-dirección 3

¿Qué instrucción está ejecutado la CPU?

Muy bien, se ejecuta M=D. Observa la salida addressM de la CPU. ¿Ves un 0x10? este número corresponde a la dirección donde se almacenará el valor en Memory. En la salida outM de la CPU ¿Puedes ver el 0x4000? Pues ese es precisamente el 16384 que será almacenado.

Te voy a preguntar algo. Si cambio el orden de las instrucciones así:

Dirección

Código ensamblador

0

@16

1

D=A

2

@16384

3

M=D

¿El resultado es el mismo?

Ejercicio 11: RETO

¿Te animas a traducir las instrucciones anteriores a lenguaje de máquina? Recuerda que ya tienes la respuesta para que compares, pero la idea es que practiques. ¿Vale?

Ejercicio 12: leer una variable e implementación de un IF

En el ejercicio anterior, la instrucción M=D guarda en memoria el valor que esté en D. ¿Recuerdas en qué dirección de memoria? La dirección será el último valor que almacenes en el registro A.

Observa las siguientes instrucciones:

Dirección

Código ensamblador

4

@24576

5

D=M

6

@19

7

D;JNE

En la instrucción D=M M está a la derecha del igual. En este caso la CPU NO está escribiendo en memoria, sino que está leyendo. ¿De qué dirección lee? La dirección será el último valor almacenado en el registro A. Por tanto, en este caso la CPU está guardando en el registro D el contenido de la dirección de memoria 24576. ¿Recuerdas que hay en esa dirección? MUY BIEN. Así es, ahí está el código de la tecla actualmente presionada o cero si no hay tecla presionada.

Ahora mira las instrucciones @19 y D;JNE. La primera almacena un 19 en el registro A. La segunda hace UNA OPERACIÓN Y UN SALTO. Nota que el resultado de la operación NO SE GUARDA. ¿Cual es la operación? Leer el contenido del registro D. En ese registro previamente se había almacenado el valor de la posición de memoria 24576. Si D es igual a cero quiere decir que no se está presionando una tecla. Si D es diferente a cero indica que se está presionando una tecla. ¿Puedes ver entonces lo que pasa? Si se está presionando una tecla el valor de D será diferente de cero. Por tanto, la operación será diferente de cero y el contador de programa se escribirá con un 19. Por consiguiente, la próxima instrucción a ejecutar no será la que esté almacenada en la dirección 8 de la ROM sino la que esté en la posición 19.

j1 j2 j3

Mnemotécnico

Efecto

1 0 1

JNE

Si operación != 0 entonces PC = A

¿Te diste cuenta lo que pasó? Acabas de ver en vivo y en directo la implementación de una estructura de control IF-ELSE. SI hay tecla presionada se ejecutan la instrucción en la dirección 19 de la ROM, SINO se ejecuta la instrucción en la dirección 8 de la ROM.

Ahora te voy a dar un momento para que respires profundo y te seques las lágrimas. ¡NO ES PARA MENOS!

Ejercicio 13: ¡EL RETO!

Es hora de tu primera transformación Saiyajin. Con todo lo que has aprendido vas a analizar el programa en lenguaje C++ y vas a traducirlo a lenguaje ensamblador. RECUERDA que ya tienes la respuesta, pero la idea es que intentes llegar a la traducción tu mismo:

MEMORY[16] = 16384;

while (true)
{
    if (MEMORY[KEYBOARD] == 0)
    {
        if ((MEMORY[16] - 16384) > 0)
        {
            MEMORY[16] = MEMORY[16] - 1;
            MEMORY[MEMORY[16]] = 0x0000;
        }
    }
    else
    {
        if ((MEMORY[16] - 24576) < 0)
        {
            MEMORY[MEMORY[16]] = 0xFFFF;
            MEMORY[16] = MEMORY[16] + 1;
        }
    }
}

Ejercicio 14: porque te encanta leer

¿Quieres leer un poco más? Te voy a dejar un enlace al capítulo 4 de uno de nuestros textos guía aquí.

Responde las siguientes preguntas:

  1. Muestra una instrucción tipo A en representación simbólica y en lenguaje de máquina. Explica qué hace esta instrucción.

  2. Muestra una instrucción tipo C en representación simbólica y en lenguaje de máquina. Explica qué hace esta instrucción.

  3. En el lenguaje hack (lenguaje ensamblador de la CPU estudiada) ¿Qué son los símbolos? muestra varios ejemplos de estos.

  4. ¿Qué son los labels? ¿Para qué sirven? ¿En que se diferencian de los símbolos?

Ejercicio 15: practica con otro ejercicio

Con este ejercicio vas a practicar la traducción a lenguaje ensamblador partiendo de un programa en alto nivel. Para escribir el programa en Visual Studio Code te voy a pedir que instales una herramienta que reconozca el lenguaje ensamblador del procesador que has estado estudiando. El objetivo es que el código se vea más bonito. Vas a instalar esta extensión así:

  • Abre Visual Studio Code.

  • Ingresa al buscador de extensiones. Lo encuentras dando click al ícono con los cuatro cuadrados, al lado izquierdo de la interfaz de usuario.

  • En el campo search extensions in marketplace puedes escribir la palabra nand2tetris. El autor de la extensión es Louis.

  • Click en install.

En la página 65 del capítulo 4 del texto guía tienes el siguiente programa:

int i = 1;
int sum = 0;
While (i <= 100){
  sum += i;
  i++;
}

La idea es que hagas la traducción de este programa a lenguaje ensamblador.

Advertencia

IMPORTANTE

En la página 65 del capítulo 4 está la solución, PERO TRATA SIN VER, luego compara, analiza, entiende y corrige.

Sigue estos pasos:

  • Crea un directorio para este ejercicio.

  • Abre el directorio en Visual Studio Code.

  • Crea un nuevo archivo que puedes llamar ex15.asm.

  • Inicia la traducción.

Advertencia

NO TE RINDAS, ESTO NO SERÁ FÁCIL.

Te pido que por favor no te rindas ni desanimes. Este ejercicio no será fácil. De hecho, vas a poner a PRUEBA lo que has aprendido hasta ahora. Si no entiendes algo te pediré que regreses y ANALICES de nuevo el material.

Una vez escribas el código en ensamblador vas a verificar que la sintaxis está correcta, es decir, cumpliste todas las reglas del lenguaje y estás usando instrucciones válidas. Luego lo vas a simular para verificar que es correcto desde el punto de vista semántico. De nuevo, necesitarás más herramientas:

  • Descarga este archivo.

  • Descomprime el archivo descargado.

  • Abre el directorio tools y ejecuta el archivo CPUEmulator.bat.

  • Amplia el tamaño de la ventana del simulador en la parte inferior para que puedas ver el campo de texto donde aparecerán los mensajes de error.

  • Carga en la memoria ROM el archivo .asm. Si la sintaxis es correcta puedes comenzar a simular tu programa. De lo contrario debes reparar la sintaxis del programa.

  • Cada vez que modifiques el programa debes cargarlo de nuevo en la memoria ROM y verificar de nuevo que no tenga errores. ¿Cómo sabes que no tiene errores? Porque el programa se podrá cargar y no tendrás mensajes de color rojo en la parte inferior de la ventana del simulador.

  • Ahora vas a probar que el programa haga lo que se supone debe hacer, es decir, que esté bien la semántica.

  • Para simular das click en el botón single step (ícono con una sola flecha azul apuntando a la derecha) o usa la tecla F11.

  • Observa los registros: PC, A y D. Observa el bloque denominado ALU. Este bloque es la parte de la CPU que realiza las operaciones. El bloque denominado RAM es el mismo bloque que llamamos Memory.

Truco

EL SIMULADOR TIENE UN MANUAL

Puedes aprender más sobre esta y otras herramientas en este enlace.

Por último te voy a pedir que pruebes otra herramienta. Se llama el Assembler.bat.

Carga el archivo .asm y dale click al botón Fast Translation. Podrás ver el código en lenguaje de máquina.

Truco

Observa el código en comentarios (//) y su traducción

//int i = 1;
//int sum = 0;
//While (i <= 100){
//  sum += i;
//  i++;
//}

//int i = 1;
@i
M = 1  // Memory[A] o Memory[16] = 1
//int sum = 0;
@sum
M = 0
(LOOP)
//While (i <= 100){
// i <= 100 --> i - 100 <= 0
@i
D = M
@100
D = D - A
@END
D;JGT
//  sum += i;
//  sum = sum + i;
@i
D = M
@sum
M = M + D
//  i++;
// i = i + 1
@i
M = M + 1
@LOOP
0;JMP
//}
(END)
@END
0;JMP

Ejercicio 16: retrieval practice

Vas a repetir los pasos del ejercicio anterior, pero cambiando de programa. Esta vez con el ejemplo inicial de la Unidad, es decir, traduce el siguiente código a lenguaje ensamblador. Una vez termines compara tu solución con la que hemos venido discutiendo. Analiza, entiende y corrige. Escribe en Visual Studio Code el lenguaje ensamblador, usa CPUEmulator.bat y Assembler.bat.

MEMORY[16] = 16384;

while (true)
{
    if (MEMORY[KEYBOARD] == 0)
    {
        if ((MEMORY[16] - 16384) > 0)
        {
            MEMORY[16] = MEMORY[16] - 1;
            MEMORY[MEMORY[16]] = 0x0000;
        }
    }
    else
    {
        if ((MEMORY[16] - 24576) < 0)
        {
            MEMORY[MEMORY[16]] = 0xFFFF;
            MEMORY[16] = MEMORY[16] + 1;
        }
    }
}

Ejercicio 17: evaluación formativa

El problema está divido en dos challenges. Tu programa debe cumplir exitosamente ambos challenges.

  • Challenge 1: leer el teclado y llenar la pantalla de negro si la tecla leída es la letra F. Volver a repetir este proceso infinitamente (loop infinito).

  • Challenge 2: leer el teclado y llenar la pantalla de negro si la tecla leída es la letra F y limpiar la pantalla si la letra leída es la C. Repetir infinitamente este proceso (loop infinito).

Consideraciones para realizar la evaluación:

  • Aquí está el enlace de la evaluación.

  • CLONA el repositorio.

  • Cámbiate al directorio problem.

  • edita ÚNICAMENTE el archivo program.asm.

  • No olvides hacer commits y push.

  • Para probar el programa de manera gráfica, puedes copiar el contenido del archivo program.asm para probar localmente en Windows con CPUEmulator.bat.

  • Al hacer las pruebas te recomiendo colocar la animación en FAST y con la opción No Animation. No olvides que debes dar click en el botón del teclado para que el programa reciba las teclas que presionarás.

  • También puedes hacer pruebas automáticas. En este caso usarás la línea de comandos. Cámbiate al directorio problem y luego ejecuta:

    Para el challenge 1:

    ../tools/CPUEmulator.sh programBasic.tst
    

    Para el challenge 2:

    ../tools/CPUEmulator.sh program.tst
    

    Si tienes éxito verás el mensaje End of script - Comparison ended successfully. De lo contrario te aparecerá un mensaje que indicará la línea del archivo .out que no coincide con el vector de prueba en el archivo .cmp.

  • Ten en cuenta que cada que hagas push al repositorio remoto, las pruebas anteriores se ejecutarán automáticamente y podrás ver el resultado en Github.

Advertencia

ALERTA DE SPOILER

En este enlace podrás consultar y clonar el repositorio con una posible solución a la evaluación formativa.