Práctica 3 : Aritmética booleana y lógica secuencial

Introducción

¿Cuál es el objetivo de cada uno de esos proyectos con sus palabras y qué se debe hacer para desarrollarlo?


Para el primer proyecto, se construirá gradualmente un conjunto de chips, culminando con la construcción del chip ALU del ordenador Hack. En este caso, partiremos de las compuertas lógicas creadas en el proyecto anterior, con el fin construir diseños lógicos de compuertas que representan números, y a su vez, realizan operaciones aritméticas con ellos. Para el otro proyecto, se construirá gradualmente una unidad RAM. Esto implica dos cuestiones principales: (i) cómo utilizar la lógica de compuertas para almacenar bits de forma persistente, a lo largo del tiempo, y (ii) cómo utilizar la lógica de compuertas para localizar ("direccionar") el registro de memoria sobre el que queremos operar.

En resumidas cuentas, el objetivo principal de este proyecto es construir todos los chips necesarios para la unidad lógica aritmética (ALU) a partir de los chips construidos en la segunda práctica. Además de construir todos los chips necesarios para el subsistema de almacenamiento, la unidad de memoria de acceso aleatorio (RAM), incluyendo bits, registradores, bancos de memoria direccionables de hasta 16k y un contador de programa a partir de los Flip-Flop, los chips que se irán construyendo y todos los chips construidos en la práctica anterior. Finalmente, se utilizó el software The Nand2tetris Software Suite, el cual contiene todas las herramientas y archivos necesarios para completar la práctica.

HalfAdder

El Half Adder es un circuito lógico combinacional y este se usa para sumar dos dígitos de un bit, obteniendo así dos salidas, la suma y el acarreo.

La función lógica que más se acomoda a los valores del carry (acarreo) es AND ya que siempre que el carry es verdadero A y B es 1. Por el lado, de salida sum (suma) es verdadera solo cuando A es verdadero y B es falso, y viceversa por lo que la mejor función lógica aplicable es la XOR.

HalfAdder.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/02/HalfAdder.hdl


/**

* Computes the sum of two bits.

*/


CHIP HalfAdder {

IN a, b; // 1-bit inputs

OUT sum, // Right bit of a + b

carry; // Left bit of a + b


PARTS:

//Como dentro del procesador lo más importante es la ALU, la unidad aritmetica lógica

//y dentro de la ALU lo que hay básicamente un acumulador y un sumador, por lo que en este projecto

//lo que se hará es construir todos estos chips que conforman la ALU.

//Como en un sumador de dos entradas se debe tener la salida carry y la sum

//la función logica del carry(acarreo) es and ya que siempre que el carry es verdadero

//las entradas A y B son 1. Por el lado, sum(suma) es verdad solo cuando A es verdadero y B es falso, y viceversa

//por lo que la mejor función lógica aplicable es la XOR

And(a=a, b=b, out=carry);

Xor(a=a, b=b, out=sum);

}

Resultados obtenidos

FullAdder

El sumador completo o Full Adder es un circuito combinacional que se utiliza para sumar tres dígitos de un bit, por lo que requiere tiene tres entradas, A, B y el acarreo de entrada además de dos salidas, la suma y el acarreo.

Por lo que debemos utilizar para su construcción dos compuertas XOR para la suma y dos compuertas AND y una OR para el carry de salida.

FullAdder.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/02/FullAdder.hdl


/**

* Computes the sum of three bits.

*/


CHIP FullAdder {

IN a, b, c; // 1-bit inputs

OUT sum, // Right bit of a + b + c

carry; // Left bit of a + b + c


PARTS:

//Como en el chip anterior solo podíamos sumar A y B y nos guardaba un carry de salida, debemos crear un

//chip que nos sume A, B y el carry de entrada (de la suma anterior), para realizar sumas más completas

// y que tenga como salida la suma y el carry de salida, para ello

// debemos utilizar dos compuertas XOR para la suma y dos compuertas

// and y una or para el carry de salida.

//A B Cin | Cout S

//0 0 0 | 0 0

//0 0 1 | 0 1

//0 1 0 | 0 1

//0 1 1 | 1 0

//1 0 0 | 0 1

//1 0 1 | 1 0

//1 1 0 | 1 0

//1 1 1 | 1 1

Xor(a=a, b=b, out=xorab);

Xor(a=xorab, b=c, out=sum);

And(a=c, b=xorab, out=cinandxorab);

And(a=a, b=b, out=andab);

Or(a=cinandxorab, b=andab, out=carry);


}


Resultados obtenidos

Add16

Este chip es muy similar al Full Adder, solo que acá estamos manejando dos entradas de 16 bit.


Para su construcción se necesitaron de 15 chips FullAdder y 1 chip HalfAdder. El chip HalfAdder se utilizó, ya que al inicio como tal no tenemos un acarreo de entrada, y los chips FullAdder se utilizaron para tomar el primer acarreo de salida del HalfAdder y que obtenga la respectiva suma y le pase al siguiente FullAdder el acarreo de salida de este y así sucesivamente con los chips FullAdder restantes.

Add16.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/02/Adder16.hdl


/**

* Adds two 16-bit values.

* The most significant carry bit is ignored.

*/


CHIP Add16 {

IN a[16], b[16];

OUT out[16];


PARTS:

// Este chip lo que hará es lo mismo que el FullAdder solo que va a ser diseñado

// para sumar dos números de 16 bit, por lo que para su construcción se utilizarán

// 15 FullAdder y 1 HalfAdder para evitar dejar un carry de entrada "suelto" al inicio

// Este HalfAdder simplemente suma a y b, se utiliza porque al inicio no tenemos como tal

// un valor de carry de entrada

HalfAdder(a=a[0], b=b[0], sum=out[0], carry=carry0);

// Lo siguiente que se hará es tomar el carry de salida del HalfAdder anterior,

// y se lo llevamos al primer FullAdder que hará otra suma y generará otro carry de salida

// el cual se le pasará al siguiente FullAdder y así sucesivamente

FullAdder(a=a[1], b=b[1], c=carry0, sum=out[1], carry=carry1);

FullAdder(a=a[2], b=b[2], c=carry1, sum=out[2], carry=carry2);

FullAdder(a=a[3], b=b[3], c=carry2, sum=out[3], carry=carry3);

FullAdder(a=a[4], b=b[4], c=carry3, sum=out[4], carry=carry4);

FullAdder(a=a[5], b=b[5], c=carry4, sum=out[5], carry=carry5);

FullAdder(a=a[6], b=b[6], c=carry5, sum=out[6], carry=carry6);

FullAdder(a=a[7], b=b[7], c=carry6, sum=out[7], carry=carry7);

FullAdder(a=a[8], b=b[8], c=carry7, sum=out[8], carry=carry8);

FullAdder(a=a[9], b=b[9], c=carry8, sum=out[9], carry=carry9);

FullAdder(a=a[10], b=b[10], c=carry9, sum=out[10], carry=carry10);

FullAdder(a=a[11], b=b[11], c=carry10, sum=out[11], carry=carry11);

FullAdder(a=a[12], b=b[12], c=carry11, sum=out[12], carry=carry12);

FullAdder(a=a[13], b=b[13], c=carry12, sum=out[13], carry=carry13);

FullAdder(a=a[14], b=b[14], c=carry13, sum=out[14], carry=carry14);

FullAdder(a=a[15], b=b[15], c=carry14, sum=out[15], carry=carry15);

}

Resultados obtenidos

Inc16

Este chip es un incrementador, y su función es sumar la constante 1 al registro de entrada. Para su construcción se utilizaron 16 HalfAdder (medio sumador) para soportar la entrada de 16 bit.

Inc16.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/02/Inc16.hdl


/**

* 16-bit incrementer:

* out = in + 1 (arithmetic addition)

*/


CHIP Inc16 {

IN in[16];

OUT out[16];


PARTS:

// Put you code here:

// Este chip es un tanto especial, ya que su función es sumar la constante 1 a un número dado

// y para su construcción se utilizaron 16 HalfAdder(medio sumador)

HalfAdder(a=in[0], b=true, sum=out[0], carry=carry1);

HalfAdder(a=in[1], b=carry1, sum=out[1], carry=carry2);

HalfAdder(a=in[2], b=carry2, sum=out[2], carry=carry3);

HalfAdder(a=in[3], b=carry3, sum=out[3], carry=carry4);

HalfAdder(a=in[4], b=carry4, sum=out[4], carry=carry5);

HalfAdder(a=in[5], b=carry5, sum=out[5], carry=carry6);

HalfAdder(a=in[6], b=carry6, sum=out[6], carry=carry7);

HalfAdder(a=in[7], b=carry7, sum=out[7], carry=carry8);

HalfAdder(a=in[8], b=carry8, sum=out[8], carry=carry9);

HalfAdder(a=in[9], b=carry9, sum=out[9], carry=carry10);

HalfAdder(a=in[10], b=carry10, sum=out[10], carry=carry11);

HalfAdder(a=in[11], b=carry11, sum=out[11], carry=carry12);

HalfAdder(a=in[12], b=carry12, sum=out[12], carry=carry13);

HalfAdder(a=in[13], b=carry13, sum=out[13], carry=carry14);

HalfAdder(a=in[14], b=carry14, sum=out[14], carry=carry15);

HalfAdder(a=in[15], b=carry15, sum=out[15]);

}

Resultados obtenidos

ALU

El chip ALU o unidad aritmética lógica es un circuito que permite, como su nombre lo indica, realizar operaciones lógicas y aritméticas y bit a bit en números binarios enteros.

Para su construcción se utilizaron varias compuertas lógicas básicas del proyecto anterior así como sus variaciones multibit y multiway (Mux16, Or8Way, etc).

Las funciones principales del ALU son convertir en cero las entradas, negar las entradas y salidas, y sumar las entradas al igual que compararlas a partir de unas banderas.

ALU.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/02/ALU.hdl


/**

* The ALU (Arithmetic Logic Unit).

* Computes one of the following functions:

* x+y, x-y, y-x, 0, 1, -1, x, y, -x, -y, !x, !y,

* x+1, y+1, x-1, y-1, x&y, x|y on two 16-bit inputs,

* according to 6 input bits denoted zx,nx,zy,ny,f,no.

* In addition, the ALU computes two 1-bit outputs:

* if the ALU output == 0, zr is set to 1; otherwise zr is set to 0;

* if the ALU output < 0, ng is set to 1; otherwise ng is set to 0.

*/


// Implementation: the ALU logic manipulates the x and y inputs

// and operates on the resulting values, as follows:

// if (zx == 1) set x = 0 // 16-bit constant

// if (nx == 1) set x = !x // bitwise not

// if (zy == 1) set y = 0 // 16-bit constant

// if (ny == 1) set y = !y // bitwise not

// if (f == 1) set out = x + y // integer 2's complement addition

// if (f == 0) set out = x & y // bitwise and

// if (no == 1) set out = !out // bitwise not

// if (out == 0) set zr = 1

// if (out < 0) set ng = 1


// Por último se tiene ya la ALU, la unidad lógica aritmética el cual

// realiza operaciones aritméticas y bit a bit en números binarios enteros.



// Para su construcción se utilizarán varias compuertas lógicas básicas del proyecto anterior

// así como sus variaciones multibit y multiway (Mux16, Or8Way, etc)


// Las funciones principales del ALU son convertir en cero las entradas, negar las entradas y salidas,

// sumar las entradas al igual que compararlas.



CHIP ALU {

IN

x[16], y[16], // 16-bit inputs

zx, // zero the x input?

nx, // negate the x input?

zy, // zero the y input?

ny, // negate the y input?

f, // compute out = x + y (if 1) or x & y (if 0)

no; // negate the out output?


OUT

out[16], // 16-bit output

zr, // 1 if (out == 0), 0 otherwise

ng; // 1 if (out < 0), 0 otherwise


PARTS:

Mux16 (a=x, b=false, sel=zx, out=w1); // if (zx == 1) set x = 0

Mux16 (a=y, b=false, sel=zy, out=w2); // if (zy == 1) set y = 0


Not16 (in=w1, out=notw1);

Mux16 (a=w1, b=notw1, sel=nx, out=w3);


Not16 (in=w2, out=notw2);

Mux16 (a=w2, b=notw2, sel=ny, out=w4);


And16 (a=w3, b=w4, out=xandy);

Add16 (a=w3, b=w4, out=addxy);

Mux16 (a=xandy, b=addxy, sel=f, out=w5);


Not16 (in=w5, out=notw5);

Mux16 (a=w5, b=notw5, sel=no, out=out, out[0..7]=out0to7, out[8..15]=out8to15, out[15]=ng);


Or8Way (in=out0to7, out=or0to7);

Or8Way (in=out8to15, out=or8to15);

Or (a=or0to7, b=or8to15, out=or0to16);

Not (in=or0to16, out=zr);

}

Resultados obtenidos

Proyecto 3. Lógica secuencial

Bit

En los proyectos anteriores, cuando se trabajó con compuertas y la propia ALU, se pudo observar que son todos combinatorios, o sea, que si alimentamos con una entrada, nos entrega algo instantáneamente. Estos sistemas no memorizan nada. Para ello necesitamos un conjunto de compuertas configurado de cierta forma, es decir, un chip flip flop D para recordar 1 bit de dato.

El problema con el flip flop tipo D, es que con él solo podemos recordar los datos en un ciclo de reloj, por lo que no podemos guardar un estado para siempre, independientemente de si las entradas cambiasen. Por lo que, tendremos que construir y usar el "Bit". Y asimismo, debemos utilizar el flip flop tipo D y la MUX para memorizar datos en múltiples ciclos.

Bit.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/03/a/Bit.hdl




/**


* 1-bit register:


* If load[t] == 1 then out[t+1] = in[t]


* else out does not change (out[t+1] = out[t])


*/




CHIP Bit {


IN in, load;


OUT out;


//La entrada del flip flop D es la salida del MUX, si el load siempre es 0,


//la salida del MUX siempre será la salida del flip flop D anterior, y la salida del


//flip flop D siempre será su entrada anterior, y nunca cambiará. Pero si el load se


//cambia a 1, la salida del MUX entrará en el ciclo del reloj, pero la salida del flip flop D


//seguirá siendo la salida anterior, que no se puede cambiar en este ciclo, pero en el próx


//ciclo, se cambiará la salida en la entrada actual


PARTS:

Mux(a=oL, b=in, sel=load, out=out1);

DFF(in=out1, out=oL, out=out);


}




Resultados obtenidos

Register

Como ya vimos el Bit, vamos a pasar a los registros. Estos no son más que una combinación de Bits. Por lo que vamos a trabajar con un arreglo, en este caso de 16 Bits, tendremos que asociar las entradas al bit de salida.

Estos son importantes ya que si tenemos Bits, lo tenemos todo porque estos son los que usaremos en la RAM debido a que está compuesta de registros.

Register.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/03/a/Register.hdl


/**

* 16-bit register:

* If load[t] == 1 then out[t+1] = in[t]

* else out does not change

*/


CHIP Register {

IN in[16], load;

OUT out[16];


PARTS:

//Como ya vimos el Bit ahora vamos a hacer un arreglo de

//16 Bits por lo que se tendrá que asociar las entradas al bit de salida

Bit(in=in[0], load=load, out=out[0]);

Bit(in=in[1], load=load, out=out[1]);

Bit(in=in[2], load=load, out=out[2]);

Bit(in=in[3], load=load, out=out[3]);

Bit(in=in[4], load=load, out=out[4]);

Bit(in=in[5], load=load, out=out[5]);

Bit(in=in[6], load=load, out=out[6]);

Bit(in=in[7], load=load, out=out[7]);

Bit(in=in[8], load=load, out=out[8]);

Bit(in=in[9], load=load, out=out[9]);

Bit(in=in[10], load=load, out=out[10]);

Bit(in=in[11], load=load, out=out[11]);

Bit(in=in[12], load=load, out=out[12]);

Bit(in=in[13], load=load, out=out[13]);

Bit(in=in[14], load=load, out=out[14]);

Bit(in=in[15], load=load, out=out[15]);

}


Resultados obtenidos

RAM8

La RAM está compuerta por registros, puede tomar entradas, también tiene que tomar un dato muy importante, que es la dirección. El sistema tiene que saber en qué dirección queremos que opere. En un solo ciclo de reloj, solo se puede acceder y manipular una dirección RAM.

Para construir el chip RAM8, tendremos que manejar un arreglo de ocho registros, ahora, tenemos que hacer que dado cierto valor de dirección, tome la entrada y la cargue en el registro seleccionado. Por otro lado, tenemos que, dado cierto valor de dirección seleccione el registro correcto y lleve su valor de salida a la salida del chip.

RAM8.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/03/a/RAM8.hdl


/**

* Memory of 8 registers, each 16 bit-wide. Out holds the value

* stored at the memory location specified by address. If load==1, then

* the in value is loaded into the memory location specified by address

* (the loaded value will be emitted to out from the next time step onward).

*/


CHIP RAM8 {

IN in[16], load, address[3];

OUT out[16];


PARTS:

//Para construir el chip RAM8, tendremos que manejar un arreglo de ocho registros, ahora,

//tenemos que hacer que dado cierto valor de dirección, tome la entrada y la cargue

//en el registro seleccionado. Esto lo haremos con el chip DMux8Way, nos dará el valor

//de los loads para los chips Register. Luego con la ayuda del chip Mux8Way16

//elegiremos cual de las 8 salidas (estas las tenemos gracias a los registros) retornaremos,

//que será en base a la dirección

//Por otro lado, tenemos que, dado cierto valor de dirección

//seleccione el registro correcto y lleve su valor de salida a la salida del chip.

//"in" son los datos de entrada

//"load" es decidir si lo guardamos o no

//"address" le dice a la RAM dónde guardar los datos, o qué ranura queremos mirar en este momento.

DMux8Way(in=load, sel=address, a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);


Register(in=in, load=load0, out=out0);

Register(in=in, load=load1, out=out1);

Register(in=in, load=load2, out=out2);

Register(in=in, load=load3, out=out3);

Register(in=in, load=load4, out=out4);

Register(in=in, load=load5, out=out5);

Register(in=in, load=load6, out=out6);

Register(in=in, load=load7, out=out7);



Mux8Way16(a=out0, b=out1, c=out2, d=out3, e=out4, f=out5, g=out6, h=out7, sel=address, out=out);

}

Resultados obtenidos

RAM64

Podemos construir el chip RAM64 recursiva, a partir de unidades de memoria más pequeñas. Por lo que podemos construir una RAM de 64 registros a partir de un un arreglo de ocho chips de RAM de 8 registros. Para seleccionar un registro particular de la memoria RAM64, usamos una dirección de 6 bits. Los bits más significativos seleccionan uno de los chips RAM8 y los bits menos significativos seleccionan uno de los registros dentro del chip RAM8 seleccionado.

En resumidas cuentas, el chip RAM64 debe estar conformado con circuitos lógicos que efectúen este esquema de direccionamiento jerárquico. Por lo que vamos a tener que usar, los chips RAM8, DMux8Way y Mux8Way16.

RAM64.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/03/a/RAM64.hdl


/**

* Memory of 64 registers, each 16 bit-wide. Out holds the value

* stored at the memory location specified by address. If load==1, then

* the in value is loaded into the memory location specified by address

* (the loaded value will be emitted to out from the next time step onward).

*/


CHIP RAM64 {

IN in[16], load, address[6];

OUT out[16];


PARTS:

//esta RAM tiene 8 registros, si la carga es 1, significa que tenemos un valor de 16 bits y queremos guardarlo

//en una de estas ranuras, la ubicación está representada por la address[3], primero usamos este DMux8Way

//para guardar la entrada en una de las cargas, si la carga es 1, una de los loads (load0, load1, load2...load7) será 1,

//el DMux simplemente la distribuye a la ubicación correcta. load es 1, significa que definitivamente tendremos

//que guardar algo

DMux8Way(in=load, sel=address[3..5], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);



RAM8(in=in, load=load0, address=address[0..2], out=out0);

RAM8(in=in, load=load1, address=address[0..2], out=out1);

RAM8(in=in, load=load2, address=address[0..2], out=out2);

RAM8(in=in, load=load3, address=address[0..2], out=out3);

RAM8(in=in, load=load4, address=address[0..2], out=out4);

RAM8(in=in, load=load5, address=address[0..2], out=out5);

RAM8(in=in, load=load6, address=address[0..2], out=out6);

RAM8(in=in, load=load7, address=address[0..2], out=out7);


Mux8Way16(a=out0, b=out1, c=out2, d=out3, e=out4, f=out5, g=out6, h=out7, sel=address[3..5], out=out);

//Este Mux de 8 entradas solo se usa para recuperar la salida del registro que queremos ver,

//si el load es 0, no le haremos ningún cambio, si el load es 1, lo que esté guardado en el

//registro se cambiará a algo más cuando lo veamos en los próximos ciclos, pero en este punto,

//la salida seguirá siendo el valor almacenado en este registro

}

Resultados obtenidos

RAM512

La metodología es la misma, se usará la misma lógica y construcción de los anteriores chip RAM64.

RAM512.hdl

// This file is part of the materials accompanying the book

// "The Elements of Computing Systems" by Nisan and Schocken,

// MIT Press. Book site: www.idc.ac.il/tecs

// File name: projects/03/b/RAM512.hdl


/**

* Memory of 512 registers, each 16 bit-wide. Out holds the value

* stored at the memory location specified by address. If load==1, then

* the in value is loaded into the memory location specified by address

* (the loaded value will be emitted to out from the next time step onward).

*/


CHIP RAM512 {

IN in[16], load, address[9];

OUT out[16];


PARTS:


//Como en las anteriores, esta RAM tiene 8 registros, si la carga es 1, significa que tenemos un valor de 16 bits y queremos guardarlo

//en una de estas ranuras, la ubicación está representada por la address[6], primero usamos este DMux8Way

//para guardar la entrada en una de las cargas, si la carga es 1, una de los loads (load0, load1, load2...load7) será 1,

//el DMux simplemente la distribuye a la ubicación correcta. load es 1, significa que definitivamente tendremos

//que guardar algo

DMux8Way(in=load, sel=address[6..8], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);


RAM64(in=in, load=load0,address=address[0..5], out=a);

RAM64(in=in, load=load1,address=address[0..5], out=b);

RAM64(in=in, load=load2,address=address[0..5], out=c);

RAM64(in=in, load=load3,address=address[0..5], out=d);

RAM64(in=in, load=load4,address=address[0..5], out=e);

RAM64(in=in, load=load5,address=address[0..5], out=f);

RAM64(in=in, load=load6,address=address[0..5], out=g);

RAM64(in=in, load=load7,address=address[0..5], out=h);


Mux8Way16(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, sel=address[6..8], out=out);

//Este Mux de 8 entradas solo se usa para recuperar la salida del registro que queremos ver,

//si el load es 0, no le haremos ningún cambio, si el load es 1, lo que esté guardado en el

//registro se cambiará a algo más cuando lo veamos en los próximos ciclos, pero en este punto,

//la salida seguirá siendo el valor almacenado en este registro

}

Resultados obtenidos

RAM4K

La metodología es la misma, se usará la misma lógica y construcción de los anteriores chip RAM64 y que el chip RAM512.

RAM4K.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/03/b/RAM4K.hdl


/**

* Memory of 4K registers, each 16 bit-wide. Out holds the value

* stored at the memory location specified by address. If load==1, then

* the in value is loaded into the memory location specified by address

* (the loaded value will be emitted to out from the next time step onward).

*/


CHIP RAM4K {

IN in[16], load, address[12];

OUT out[16];


PARTS:

//Como en las anteriores, esta RAM tiene 8 registros, si la carga es 1, significa que tenemos un valor de 16 bits y queremos guardarlo

//en una de estas ranuras, la ubicación está representada por la address[9], primero usamos este DMux8Way

//para guardar la entrada en una de las cargas, si la carga es 1, una de los loads (load0, load1, load2...load7) será 1,

//el DMux simplemente la distribuye a la ubicación correcta. load es 1, significa que definitivamente tendremos

//que guardar algo

DMux8Way(in=load, sel=address[9..11], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);


RAM512(in=in, load=load0,address=address[0..8], out=a);

RAM512(in=in, load=load1,address=address[0..8], out=b);

RAM512(in=in, load=load2,address=address[0..8], out=c);

RAM512(in=in, load=load3,address=address[0..8], out=d);

RAM512(in=in, load=load4,address=address[0..8], out=e);

RAM512(in=in, load=load5,address=address[0..8], out=f);

RAM512(in=in, load=load6,address=address[0..8], out=g);

RAM512(in=in, load=load7,address=address[0..8], out=h);


Mux8Way16(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, sel=address[9..11], out=out);

//Este Mux de 8 entradas solo se usa para recuperar la salida del registro que queremos ver,

//si el load es 0, no le haremos ningún cambio, si el load es 1, lo que esté guardado en el

//registro se cambiará a algo más cuando lo veamos en los próximos ciclos, pero en este punto,

//la salida seguirá siendo el valor almacenado en este registro

}

Resultados obtenidos

RAM16K

Este chip RAM16K se construye un poco diferente, el recuento de direcciones no aumentará en 3, sino en 2, por lo que será un DMux de 4 entradas y un MUX de 4 entradas, aparte de eso, todo usará la misma lógica e implementación de los anteriores chips RAM.

RAM16K.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/03/b/RAM16K.hdl


/**

* Memory of 16K registers, each 16 bit-wide. Out holds the value

* stored at the memory location specified by address. If load==1, then

* the in value is loaded into the memory location specified by address

* (the loaded value will be emitted to out from the next time step onward).

*/


CHIP RAM16K {

IN in[16], load, address[14];

OUT out[16];


PARTS:

//Esta RAM tiene 4 registros, si la carga es 1, significa que tenemos un valor de 16 bits y queremos guardarlo

//en una de estas ranuras, la ubicación está representada por la address[12], primero usamos este DMux4Way

//para guardar la entrada en una de las cargas, si la carga es 1, una de los loads (load0, load1, load2 y load3) será 1,

//el DMux simplemente la distribuye a la ubicación correcta. load es 1, significa que definitivamente tendremos

//que guardar algo

DMux4Way(in=load, sel=address[12..13],

a=load0, b=load1, c=load2, d=load3);



RAM4K(in=in, load=load0, address=address[0..11], out=out0);

RAM4K(in=in, load=load1, address=address[0..11], out=out1);

RAM4K(in=in, load=load2, address=address[0..11], out=out2);

RAM4K(in=in, load=load3, address=address[0..11], out=out3);


// output logic

Mux4Way16(a=out0, b=out1, c=out2, d=out3,

sel=address[12..13], out=out);

//Este Mux de 4 entradas solo se usa para recuperar la salida del registro que queremos ver,

//si el load es 0, no le haremos ningún cambio, si el load es 1, lo que esté guardado en el

//registro se cambiará a algo más cuando lo veamos en los próximos ciclos, pero en este punto,

//la salida seguirá siendo el valor almacenado en este registro

}

Resultados obtenidos

PC

En el Chip PC tenemos 4 posibles casos: resetear, sustituir, incrementar, mantener. Para decidir cual de estas 4 salidas retornamos implementamos un MUX16, primero decidimos entre mantener o incrementar, seguido de decidir entre la salida anterior y sustituir para finalmente decidir entre la salida anterior o resetear. Debido al funcionamiento de los MUX16 para implementar las condiciones en el orden indicado, estas son evaluadas.

PC.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/03/a/PC.hdl


/**

* A 16-bit counter with load and reset control bits.

* if (reset[t] == 1) out[t+1] = 0

* else if (load[t] == 1) out[t+1] = in[t]

* else if (inc[t] == 1) out[t+1] = out[t] + 1 (integer addition)

* else out[t+1] = out[t]

*/


CHIP PC {

IN in[16],load,inc,reset;

OUT out[16];


PARTS:

// Para cumplir los requerimiento que se pide se toman del

// menos al más importante(inc,load,reset) respectivamete

// comenzamos con un chip Inc16 el cual incrementará la variable dout.

// A un MUX16 se pasa como entradas la in y la incremented y como

// selector la variable inc el out de este Mux16 lo tomamos como

// una entrada para un siguiente MUX16 y la otra entrada es el vector in

// como selector tendrá load, de igual manera la salida de este será la entrada

// de otro MUX16 tomando como la otra entrada false y el sel será reset

// por ultimo este afterreset v3 será la entrada del chip Register y este dá el out final

Or(a=inc, b=load, out=incorload);

Or(a=incorload, b=reset, out=threeor);

//este "threeor" es para determinar si hacemos cambios en el valor del Registro

Inc16(in=dout, out=incremented);

Mux16(a=in, b=incremented, sel=inc, out=afterinc);

Mux16(a=afterinc, b=in, sel=load, out=afterload);

Mux16(a=afterload, b=false, sel=reset, out=afterreset);

Register(in=afterreset, load=threeor, out=dout, out=out);

}



Resultados obtenidos