Práctica 4 : Lenguaje y Arquitectura
Introducción
¿Cuál es el objetivo de cada uno de esos proyectos con sus palabras y qué se debe hacer para desarrollarlo?
El primero proyecto se enfoca en la programación de bajo nivel en lenguaje de máquina "Hack", esto es conveniente para familiarizarnos con el sistema informático por medio de la construcción de algunos programas de bajo nivel escritos en el lenguaje de máquina. Además, nos ayuda a entender por qué su hardware fue diseñado de cierta manera. Dicho esto, en este proyecto se construirán dos programas en lenguaje de maquina Hack, el primero es un programa que calcula el producto de dos ubicaciones en la RAM y el segundo es un programa interactivo que permite borrar o ennegrecer la pantalla según ciertas condiciones. Para realizarlos necesitaremos primeramente el software Assembler proporcionado por The Nand2tetris Software Suite, el cual nos permitirá programar en lenguaje maquina Hack y traducirlo a binario generando un archivo .hack el cual se lo podremos pasar al software CPUEmulator. Pero, ¿por qué no traducir directamente el código máquina a binario? Bueno, diferentes máquinas tienen diferente interpretación del código máquina la información que transmite cada línea de binarios puede ser bastante diferente. Por ejemplo la CPU Intel y la CPU AMD pueden tener una traducción totalmente diferente de la misma línea de 101010101010101010.
Por otra parte, en el proyecto 5 vamos a tomar todos los chips que hemos construido en los proyectos 1-3 con el objetivo de integrarlos en un sistema informático capaz de ejecutar programas escritos en el lenguaje de máquina. La computadora que construiremos, llamada "Hack", tiene dos virtudes importantes. Por un lado, Hack es una máquina que se puede construir, utilizando chips construidos previamente y el simulador de hardware suministrado. Por el otro, construirlo nos dará una comprensión de cómo funcionan las computadoras en los niveles bajos de hardware y software.
Multiplication
El multiplication program calcula el producto de dos ubicaciones de la RAM y almacena el resultado en otra dirección. Por lo que las entradas de este programa son los valores almacenados en R[0] y R[1] ya que como sabemos, la primera instrucción se cargaría en la dirección 0 y la siguiente en la dirección 1 o en otras palabras, las dos ubicaciones superiores de la RAM y además de esto, almacena el resultado en R[2].
Mult.asm
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/04/Mult.asm
// Multiplies R0 and R1 and stores the result in R2.
// (R0, R1, R2 refer to RAM[0], RAM[1], and RAM[2], respectively.)
//
// This program only needs to handle arguments that satisfy
// R0 >= 0, R1 >= 0, and R0*R1 < 32768.
@sum
M=0 // Inicializa sum en 0
@R1
D=M
@count
M=D // Inicializa el conteo al valor en RAM[1]
(LOOP)
@count
D=M
@END
D;JEQ // Verifique si el conteo es 0, si es así, vaya a termina (va a END)
@R0
D=M
@sum
M=M+D // Agrega RAM[0] a la suma
@count
M=M-1 // Resta el conteo en 1
@LOOP
0;JMP // Realiza el bucle de nuevo
(END)
@sum
D=M
@R2
M=D // Escribe sum en RAM[2]
Resultados obtenidos
Con el código listo que realice la multiplicación, tenemos que traducir el código ASM a código binario para preparar el código y sea realmente ejecutable en el CPUEmulator. Para ello utilizamos el Assembler suministrado para traducir el programa Mult.asm y producir un archivo Mult.hack
Mult.hack
0000000000010000
1110101010001000
0000000000000001
1111110000010000
0000000000010001
1110001100001000
0000000000010001
1111110000010000
0000000000010010
1110001100000010
0000000000000000
1111110000010000
0000000000010000
1111000010001000
0000000000010001
1111110010001000
0000000000000110
1110101010000111
0000000000010000
1111110000010000
0000000000000010
1110001100001000
Resultados obtenidos
Ahora con la herramienta CPUEmulator, se le asignarán los valores que queremos que se multipliquen. En nuestro caso, utilizamos para la dirección R[0] = 5 y para dirección R[1] = 10 para obtener en la dirección R[2] el valor 50
I/O Handling
Este es un programa interactivo el cual cuando se presiona una tecla (cualquier tecla), el programa ennegrece la pantalla (escribe "black" en cada cada pixel) y en el caso donde no se presiona ninguna tecla, la pantalla debe borrarse (escribe "white" en cada pixel).
Fill.asm
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/04/Fill.asm
// Runs an infinite loop that listens to the keyboard input.
// When a key is pressed (any key), the program blackens the screen,
// i.e. writes "black" in every pixel;
// the screen should remain fully black as long as the key is pressed.
// When no key is pressed, the program clears the screen, i.e. writes
// "white" in every pixel;
// the screen should remain fully clear as long as no key is pressed.
// Put your code here.
(RESTART)
@SCREEN
D=A
@0
M=D //Coloca el inicio de la pantalla en RAM[0]
///////////////////////////
(KBDCHECK)
@KBD
D=M
@BLACK
D;JGT //Si se presiona alguna tecla, ennegrece
@WHITE
D;JEQ //De lo contrario blanquea la pantalla
@KBDCHECK
0;JMP
(BLACK)
@1
M=-1 //Con lo que se rellena la pantalla en el caso de que se presione tecla (-1=11111111111111)
@CHANGE
0;JMP
(WHITE)
@1
M=0 //Con lo que se rellena la pantalla en el caso contrario
@CHANGE
0;JMP
(CHANGE)
@1 //Comprueba con qué rellenar la pantalla
D=M //D contiene negro o blanco
@0
A=M //Obtiene la dirección del pixel de pantalla para rellenarlo
M=D //lo rellena
@0
D=M+1 //Siguiente pixel
@KBD
D=A-D
@0
M=M+1 //Siguiente pixel
A=M
@CHANGE
D;JGT //Si A=0 toda la pantalla se vuelve negra
@RESTART
0;JMP
Resultados obtenidos
Con el código listo que realice la función interactiva de la pantalla, tenemos que traducir el código ASM a código binario para preparar el código y sea realmente ejecutable en el CPUEmulator. Para ello utilizamos el Assembler suministrado para traducir el programa Fill.asm y producir un archivo Fill.hack
Fill.hack
0100000000000000
1110110000010000
0000000000000000
1110001100001000
0110000000000000
1111110000010000
0000000000001100
1110001100000001
0000000000010000
1110001100000010
0000000000000100
1110101010000111
0000000000000001
1110111010001000
0000000000010100
1110101010000111
0000000000000001
1110101010001000
0000000000010100
1110101010000111
0000000000000001
1111110000010000
0000000000000000
1111110000100000
1110001100001000
0000000000000000
1111110111010000
0110000000000000
1110000111010000
0000000000000000
1111110111001000
1111110000100000
0000000000010100
1110001100000001
0000000000000000
1110101010000111
Resultados obtenidos
Ahora con la herramienta CPUEmulator, probamos el programa mediante el archivo Fill.hack generado con el Assembler. El cual dependiendo de las entradas este nos permitirá visualizar la pantalla en negro o blanco
Proyecto 5. Arquitectura de Computadores
MEMORY
Este chip memory, está compuesto esencialmente de una fusión de otros tres chips de niveles inferiores: RAM16K, pantalla, teclado. Aunque estos están ya incorporados por lo que no hay necesidad de construirlos. Además también cuenta con las compuertas: DMux4Way, Mux4Way16 y or.
Memory.hdl
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/Memory.hdl
/**
* The complete address space of the Hack computer's memory,
* including RAM and memory-mapped I/O.
* The chip facilitates read and write operations, as follows:
* Read: out(t) = Memory[address(t)](t)
* Write: if load(t-1) then Memory[address(t-1)](t) = in(t-1)
* In words: the chip always outputs the value stored at the memory
* location specified by address. If load==1, the in value is loaded
* into the memory location specified by address. This value becomes
* available through the out output from the next time step onward.
* Address space rules:
* Only the upper 16K+8K+1 words of the Memory chip are used.
* Access to address>0x6000 is invalid. Access to any address in
* the range 0x4000-0x5FFF results in accessing the screen memory
* map. Access to address 0x6000 results in accessing the keyboard
* memory map. The behavior in these addresses is described in the
* Screen and Keyboard chip specifications given in the book.
*/
CHIP Memory {
IN in[16], load, address[15];
OUT out[16];
PARTS:
// Put your code here:
RAM16K(in=in, load=RAMLoad, address=address[0..13], out=RAMOut);
Screen(in=in, load=ScreenLoad, address=address[0..12], out=ScreenOut);
Keyboard(out=KeyboardOut);
DMux4Way(in=load ,sel=address[13..14] ,a=RAMLoadA ,b=RAMLoadB ,c=ScreenLoad);
Or(a=RAMLoadA, b=RAMLoadB, out=RAMLoad);
Mux4Way16(a= RAMOut ,b=RAMOut ,c=ScreenOut ,d=KeyboardOut ,sel=address[13..14] ,out=out );
}
Resultados obtenidos
Por medio del simulador de hardware ejecutamos el chip "Memory.hdl" y podemos observar que al ingresarle el dato 19, decirle que lo cargue y lo guarde en la dirección 19, logra ejecutarse de manera correcta, almacenando el 19 en la dirección 19.
CPU
La unidad central de procesamiento (CPU) se construyó de acuerdo con la implementación dada en la siguiente figura,
usando los chips ALU y Register construidos en los proyectos 2 y 3, además de los ARegister y DRegister los cuales tienen la misma funcionalidad que el chip Register del proyecto 3.
CPU.hdl
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/CPU.hdl
/**
* The Hack CPU (Central Processing unit), consisting of an ALU,
* two registers named A and D, and a program counter named PC.
* The CPU is designed to fetch and execute instructions written in
* the Hack machine language. In particular, functions as follows:
* Executes the inputted instruction according to the Hack machine
* language specification. The D and A in the language specification
* refer to CPU-resident registers, while M refers to the external
* memory location addressed by A, i.e. to Memory[A]. The inM input
* holds the value of this location. If the current instruction needs
* to write a value to M, the value is placed in outM, the address
* of the target location is placed in the addressM output, and the
* writeM control bit is asserted. (When writeM==0, any value may
* appear in outM). The outM and writeM outputs are combinational:
* they are affected instantaneously by the execution of the current
* instruction. The addressM and pc outputs are clocked: although they
* are affected by the execution of the current instruction, they commit
* to their new values only in the next time step. If reset==1 then the
* CPU jumps to address 0 (i.e. pc is set to 0 in next time step) rather
* than to the address resulting from executing the current instruction.
*/
CHIP CPU {
IN inM[16], // M value input (M = contents of RAM[A])
instruction[16], // Instruction for execution
reset; // Signals whether to re-start the current
// program (reset==1) or continue executing
// the current program (reset==0).
OUT outM[16], // M value output
writeM, // Write to M?
addressM[15], // Address in data memory (of M)
pc[15]; // address of next instruction
PARTS:
// Put your code here:
Not(in=instruction[15],out=ni);
Mux16(a=outtM,b=instruction,sel=ni,out=i);
Or(a=ni,b=instruction[5],out=intoA);
ARegister(in=i,load=intoA,out=A,out[0..14]=addressM);
And(a=instruction[15],b=instruction[12],out=AorM);
Mux16(a=A,b=inM,sel=AorM,out=AM);
ALU(x=D,y=AM,zx=instruction[11],nx=instruction[10],zy=instruction[9],ny=instruction[8],f=instruction[7],no=instruction[6],out=outtM,out=outM,zr=zr,ng=ng);
And(a=instruction[15],b=instruction[4],out=intoD);
DRegister(in=outtM,load=intoD,out=D);
And(a=instruction[15],b=instruction[3],out=writeM);
Not(in=ng,out=pos);
Not(in=zr,out=nzr);
And(a=instruction[15],b=instruction[0],out=jgt);
And(a=pos,b=nzr,out=posnzr);
And(a=jgt,b=posnzr,out=ld1);
And(a=instruction[15],b=instruction[1],out=jeq);
And(a=jeq,b=zr,out=ld2);
And(a=instruction[15],b=instruction[2],out=jlt);
And(a=jlt,b=ng,out=ld3);
Or(a=ld1,b=ld2,out=ldt);
Or(a=ld3,b=ldt,out=ld);
PC(in=A,load=ld,inc=true,reset=reset,out[0..14]=pc);
}
Resultados obtenidos
En el simulador de hardware ejecutamos el archivo "CPU.hdl" y obtenemos que el pc va a buscar una instrucción. Al momento de darle 1 a reset, el pc se resetea. Y por último, cuando ingresamos 0 nuevamente en reset el pc va a volver a buscar una instrucción.
COMPUTER
El chip de computer se realizó utilizando tres partes de chips construidos anteriormente: una CPU, una memoria de datos y una memoria de instrucciones llamada ROM32K.
Computer.hdl
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/05/Computer.hdl
/**
* The HACK computer, including CPU, ROM and RAM.
* When reset is 0, the program stored in the computer's ROM executes.
* When reset is 1, the execution of the program restarts.
* Thus, to start a program's execution, reset must be pushed "up" (1)
* and "down" (0). From this point onward the user is at the mercy of
* the software. In particular, depending on the program's code, the
* screen may show some output and the user may be able to interact
* with the computer via the keyboard.
*/
CHIP Computer {
IN reset;
PARTS:
// Put your code here:
ROM32K(address=pc,out=instruction);
CPU(inM=inM,instruction=instruction,reset=reset,outM=outM,writeM=writeM,addressM=addressM,pc=pc);
Memory(in=outM,load=writeM,address=addressM,out=inM);
}
Resultados obtenidos
Gracias al simulador de hardware ejecutamos el archivo "Computer.hdl" y ejecutamos el archivo "Add.hack" que nos va a permitir sumar dos números, en este caso, primero carga el número 2, seguido carga el número 3 y nos dice que el resultado es 5, y por último lo almacena en la memoria en la posición 0.
ADD
El add suma las dos constantes 2 y 3 y escribe el resultado en RAM[0]
Add.hack
0000000000000010
1110110000010000
0000000000000011
1110000010010000
0000000000000000
1110001100001000
Resultados obtenidos
Gracias al simulador de hardware ejecutamos el archivo "Computer.hdl" y ejecutamos el archivo "ComputerAdd-external.tst" el cual nos permite realizar la respectiva operación.
MAX
El max calcula el máximo de RAM[0] y RAM[1] y escribe el resultado en RAM[2].
Max.hack
0000000000000000
1111110000010000
0000000000000001
1111010011010000
0000000000001010
1110001100000001
0000000000000001
1111110000010000
0000000000001100
1110101010000111
0000000000000000
1111110000010000
0000000000000010
1110001100001000
0000000000001110
1110101010000111
Resultados obtenidos
Gracias al simulador de hardware ejecutamos el archivo "Computer.hdl" y ejecutamos el archivo "ComputerMax-external.tst", siendo posible calcular el resultado deseado.
RECT
El rect dibuja en la pantalla un rectángulo de las filas de RAM[0] de 16 píxeles cada uno.
Rect.hack
0000000000000000
1111110000010000
0000000000010111
1110001100000110
0000000000010000
1110001100001000
0100000000000000
1110110000010000
0000000000010001
1110001100001000
0000000000010001
1111110000100000
1110111010001000
0000000000010001
1111110000010000
0000000000100000
1110000010010000
0000000000010001
1110001100001000
0000000000010000
1111110010011000
0000000000001010
1110001100000001
0000000000010111
1110101010000111
Resultados obtenidos
Gracias al simulador de hardware ejecutamos el archivo "Computer.hdl" y ejecutamos el archivo "ComputerRect-external.tst", en el cual se logra observar la figura dibujada, en este caso, el rectángulo.