Extracto de la memoria del proyecto de fin de carrera:
por David Rodríguez
Un ordenador esta formado por una serie de elementos, que permiten recibir información, procesarla y emitir unos resultados. Como elementos principales podemos destacar:
Unidad central de proceso. Formada por la unidad de control, que coordina las actividades del ordenador, ejecutando programas de forma ordenada e interactuando con las unidades de entrada y salida, y la unidad aritmético-lógica (ALU) que se encarga de todas las operaciones aritméticas y lógicas.
Memoria principal. Es la parte del ordenador que almacena los programas y los datos necesarios para el correcto funcionamiento del programa que se esta ejecutando.
Periféricos. Elementos dedicados a la recepción y envío de datos.
Aunque la mayoría de ordenadores actuales están basados en el modelo de Von Neumann, el comportamiento de los microprocesadores de hoy día es bastante más complejo. YASP implementa un procesador elemental, y a pesar de tener una funcionalidad bastante limitada nos permite entender el funcionamiento de los ordenadores actuales.
YASP implementa la unidad central de proceso (UCP), descompuesta en una unidad de control (UC) y una unidad aritmeticológica (UAL), y la memoria principal. Para completar la estructura básica de un ordenador, solo faltaría conectar los periféricos.
Esta máquina trabaja con datos de 8 bits (1 byte), que bien pueden estar almacenados en la memoria principal o provenir de un puerto de entrada.
La memoria principal esta formada por 256 bytes, por lo tanto se pueden representar todas las posiciones con un único byte (desde la 0 hasta la 255).
Se dispone de un registro de 8 bits llamado acumulador, que permite procesar los datos, de forma que el resultado de cualquier operación aritmética o lógica se guarda en este registro.
Todas estas operaciones se llevan a cabo en la unidad aritmeticológica, que en el caso de necesitar un segundo operando, lo toma de la memoria principal a través del BM (buffer de memoria). Se dispone de dos flags adicionales, de un bit cada uno, que nos permiten saber si el resultado de la operación ha sido cero o no (el bit Z) y si la operación ha generado acarreo o no (el bit C).
Para acceder a cualquier posición de memoria, es necesario dejar la dirección en el registro de direcciones de memoria (RAM) antes de efectuar la lectura del contenido de memoria o escribir el contenido del BM.
Existen cuatro formas de obtener la dirección del segundo operando de una operación binaria (el otro es el registro A):
Inmediata. El valor del operando se especifica conjuntamente con la operación.
Directa. La dirección del operando se indica con la operación. Para obtener el valor es necesario buscar en la posición indicada.
Indirecta. La dirección que indica la operación nos dice donde esta guardada la dirección del operando. Es decir, nos dice la dirección donde esta guardada la dirección del operando.
Indexada. Este modo de direccionamiento esta indicado para los casos en los que se desea trabajar con una serie de datos de los que se conoce la dirección inicial. Para obtener la dirección del operando hay que sumar el contenido del registro X a la dirección que viene con la operación.
Una vez obtenido el segundo operando, en las operaciones que lo requieran, la UAL realiza la operación entre el operando y el contenido del registro A. El resultado se deja normalmente en el registro A. Si la operación es unaria, el operando es el registro A y el resultado, igual que pasa con las operaciones binarias se guarda en el registro A.
La UAL de YASP puede ejecutar sumas, restas, cálculo del opuesto (cambios de signo), incrementos, decrementos, AND lógica, OR inclusivas y exclusivas, desplazamientos y rotaciones. También permite el paso directo de información sin realizar ninguna operación.
Tanto las operaciones que realiza la UAL como la dirección que se necesita para obtener un segundo posible operando, se indican en la instrucción que se esta ejecutando en ese mismo momento.
Esta instrucción se guarda en el registro de instrucciones (RI), así la unidad de control puede decidir que hacer en cada momento. El ciclo que ejecuta la unidad de control esta formado por estos pasos:
Búsqueda de la instrucción siguiente. La dirección de la siguiente instrucción se guarda en el contador de programa (CP). Por lo tanto la UC se encarga de obtener el contenido de la posición de memoria indicada en el CP.
Cálculo de la dirección efectiva. En el caso de que la instrucción implique hacer una operación con dos operandos, hay que leer el byte siguiente de la memoria. Dependiendo del modo de direccionamiento, este byte indica el segundo operando (direccionamiento inmediato) o la dirección necesaria para obtenerlo dependiendo del modo de direccionamiento indicado en la instrucción.
Ejecución. En esta fase, la unidad de control dirige a los elementos de la máquina para llevar a cabo la operación indicada en la instrucción.
Cálculo de la instrucción siguiente. Generalmente, la siguiente instrucción se encuentra a continuación de la que queremos ejecutar, por lo que hay suficiente con incrementar el CP. En algunas ocasiones, puede ser necesario saltar a un determinado punto del programa, por lo que se deben utilizar saltos incondicionales o condicionales. Los saltos condicionales, dependen del estado de la máquina, y están definidos por los bits C i Z. En este tipo de saltos, solo se puede saltar 16 posiciones hacia delante o 15 hacia atrás. Estas instrucciones utilizan un modo de direccionamiento relativo, por lo que la dirección efectiva se obtiene de sumar la dirección que acompaña a la instrucción y el contenido del CP. El disponer de 5 bits para indicar esta dirección, limita el rango de salto a 16 posiciones hacia delante o 15 hacia atrás.
Las operaciones que puede realizar YASP tienen un código asociado, el código de operación, que la unidad de control entiende.
Para facilitar la interpretación de estos códigos se utiliza un mnemónico que ayudan a recordar lo que hace un determinado código.
Una instrucción esta formada por un código de operación y los posibles argumentos que necesite para que se pueda llevar a cabo su función. En el caso de la suma es necesario indicar el modo de direccionamiento para encontrar el segundo operando y la dirección que se utilizará para calcular la dirección efectiva.
Por lo tanto, a la secuencia de instrucciones se las denomina lenguaje máquina, porque están escritas en un lenguaje que la máquina puede entender.
A continuación se describe el repertorio de instrucciones del YASP agrupadas por su función. Para simplificar se utiliza arg para indicar el argumento que acompaña al código de operación.
Estas instrucciones transfieren la información entre diferentes elementos de la máquina sin modificar su valor.
LDA arg El operando de deja en el registro A o X. LDX arg
STA arg Se guarda el registro A o X en la dirección de memoria efectiva
STX arg indicada por el operando. Esta instrucción no dispone de direccionamiento inmediato, porque no se puede guardar un valor en otro valor.
TAX La instrucción TAX transfiere el contenido del registro A al X, y TXA TXA hace lo contrario.
Estas instrucciones afectan al bit de carry (C) y zero (Z), que guardan el estado de la operación hecha.
ADD arg La instrucción ADD suma el operando al registro A, en cambio la
SUB arg instrucción SUB resta de A en valor del operando. Hay que tener en cuenta que SUB hace una suma con el C2 (operando). El resultado de las dos instrucciones se guarda en el registro A.
CMP arg Efectúa una resta, pero no deja el resultado en el acumulador. Esta instrucción solo sirve para modificar el estado de la máquina.
NEG Calcula el complemento a dos del acumulador y deja el resultado.
INA Incrementa y decrementa el acumulador en una unidad. Hay que tener DCA en cuenta que 0-1=255, con C=1; y que 255+1=0, con C=1.
INX Incrementa o decrementa el registro X en una unidad. DCX
Estas instrucciones afectan al bit de zero (Z), que podrá ser consultado más tarde.
AND arg | Realizan la operación indicada bit a bit entre el acumulador y el |
OR arg | operando. El resultado se guarda en el acumulador. |
XOR arg | |
TST arg | Realiza una AND pero no guarda el resultado en el acumulador. Sirve |
para modificar el bit Z. Su principal utilidad es descubrir si un | |
determinado bit del acumulador es 1. | |
NOT | Calcula el complemento a uno del acumulador y deja el resultado. |
JMP arg Cambia el contenido del contador de programa por la dirección efectiva calculada a partir de arg. El salto incondicional no dispone de forma inmediata.
JC arg Efectúan saltos condicionales sumando el valor de arg al contador de JNC arg programa. En la suma se considera que arg se da en complemento a 2, JZ arg de esta forma el salto se puede realizar hacia delante o hacia detrás JNZ arg dependiendo del signo de arg.
El salto se efectuará dependiendo de si el bit de carry (C) es 0 (JNC) o 1 (JC), o si el bit de zero (Z) es 0 (JNZ ) o 1 (JZ). En el caso contrario no se efectúa ningún salto. Hay que tener en cuenta que la instrucción JC -1 puede ser un bucle infinito si C=1, porque efectuará el salto decrementando el CP en 1; es decir situándose como siguiente instrucción.
HLT Para la máquina. La máquina se mantiene inactiva hasta que se hace un reset y se inicia la ejecución en la instrucción que se encuentre en la posición 0 de la memoria.
A veces puede ser necesario acceder a los bits de un byte; hay un conjunto de instrucciones que permiten acceder a los bits del acumulador de forma rápida e individual. Estas instrucciones afectan el bit de zero, que se pone a 1 cuando todos los bits del acumulador son 0, y al bit de carry que toma el valor del bit que se desplaza entre los extremos del acumulador.
ROL Efectúa una rotación a la izquierda de los bits del acumulador. De forma que el bit más significativo pasa a ser el menos significativo y también se asigna al indicador de acarreo:
ROR Efectúa una rotación a la derecha de los bits del acumulador. Deja el bit menos significativo como bit de acarreo:
RCL Realiza una rotación a la izquierda de los bits del acumulador y del bit de acarreo. El bit más significativo del acumulador se copia al indicador C y este a la posición menos significativa del acumulador:
RCR Realiza una rotación de los bits del acumulador y de los de acarreo una posición a la derecha:
SHL Desplaza los bits del acumulador una posición hacia la izquierda, pone un 0 como bit menos significativo del acumulador y copia el bit más significativo en el indicador de acarreo:
Esta operación equivale a multiplicar por 2 el valor del registro A.
SHR Desplaza los bits del acumulador una posición hacia la derecha. El bit más significativo se pone a 0. El bit menos significativo pasa al indicador de acarreo:
Esta operación equivale a dividir enteramente por 2 el número natural que se guarda en el registro A.
SCL Desplaza los bits del acumulador una posición hacia la izquierda. El bit más significativo se pierde y el menos significativo toma el valor del
SCR Efectúa un desplazamiento de los bits del registro A una posición hacia la derecha de manera que el bit más significativo toma el valor del de
Esta operación equivale a dividir el contenido del acumulador por 2, siempre que el bit C tenga el mismo valor que el bit de signo del número que se guarda. Si se trabaja con números naturales el acarreo debe ser 0.
CLC Estas instrucciones permiten apagar el bit de acarreo (CLC) o activarlo STC (STC).
La máquina elemental puede enviar y recibir datos a través de los puertos, que transfieren la información desde o hacia el acumulador.
Hay 8 puertos de entrada y otros 8 de salida, identificados con los números comprendidos entre el 0 y el 7. Hay otros 8 puertos de entrada/salida que se identifican con los números del 8 al 15.
El repertorio de instrucciones tiene dos instrucciones que permiten escribir o leer datos de los puertos. La instrucción OUT num_puerto transfiere el contenido del acumulador al puerto de salida identificado con el número num_puerto. En cambio, INP num_puerto copia la información contenida en el puerto identificado con el número num_puerto al acumulador.
Para los casos en los que no se puede determinar en que momento se debe realizar la operación de entrada/salida, la ME dispone de 8 líneas para hacer peticiones y otras 8 para recibir contestación de conformidad.
Para poner una línea de petición a 0 se utiliza la instrucción RQL num_línea, i a 1 con RQH num_línea. Las instrucciones AKL num_línea espera que haya un 0 en la línea num_línea y AKH num_línea espera a que haya un 1 en la línea num_línea. Mientras se esta esperando la conformidad de alguna línea, la máquina se detiene hasta que no encuentre el bit esperado en la línea correspondiente.
Código de operación | Nombre | Descripción en inglés |
---|---|---|
000 | JC | Jump if Carry |
001 | JNC | Jump if No Carry |
010 | JZ | Jump if Zero |
011 | JNZ | Jump if No Zero |
1000 | OUT | OUTput through port |
1001 | INP | INPut from port |
10100 | RQL | send ReQuest Low |
10101 | RQH | send ReQuest High |
10110 | AKL | wait for AcKnowledge Low |
10111 | AKH | wait for AcKnowledge High |
1100 0000 | ROL | ROtation Left |
1100 0001 | ROR | ROtation Right |
1100 0010 | RCL | ROtation (with carry) Left |
1100 0011 | RCR | ROtation (with carry) Right |
1100 0100 | SHL | SHift Left |
1100 0101 | SHR | SHift Right |
1100 0110 | SCL | SHift (with carry) Left |
1100 0111 | SCR | SHift (with carry) Right |
1100 1000 | CLC | CLear Carry |
1100 1001 | STC | SeT Carry |
1100 1010 | NOT | bitwise one’s complement of A |
1100 1011 | NEG | two’s complement of A |
1100 1100 | INA | Increment A |
1100 1101 | DCA | DeCrement A |
1100 1110 | INX | Increment X |
1100 1111 | DCX | DeCrement X |
1101 00 | LDA | LoaD A |
1101 01 | LDX | LoaD X |
1101 10 | STA | STore A |
1101 10 11 | TAX | Transfer A to X |
1101 11 | STX | STore X |
1101 11 11 | TXA | Transfer X to A |
1110 00 | TST | TeST: bitwise AND discarding result |
1110 01 | AND | AND with operand |
1110 10 | XOR | XOR with operand |
1110 11 | OR | OR with operand |
1111 00 | CMP | CoMPare: substraction discarding result |
1111 01 | SUB | SUBtract operand from A |
1111 10 | ADD | ADD operand to A |
1111 11 | JMP | JuMP to effective address |
1111 11 11 | HLT | HaLT |
Tabla 1.
Repertorio de instrucciones de la máquina elemental YASPEn este capítulo se describe la interfaz de usuario de la versión Javascript del simulador de YASP. Se trata de una interfaz muy simple en la que la información se organiza en subventanas: una para el código en ensamblador y otras para el procesador YASP. En estas últimas, una se dedica a la memoria, otra a los registros, otra a los indicadores de acarreo y de cero, otra a los puertos de entrada y salida y, finalmente, otra a las líneas de petición y aceptación. (En una ventana adicional se muestra un pequeño display conectado a los puertos de salida, para que los programas puedan mostrar al usuario su estado de funcionamiento.) La modificación de los datos del procesador por parte del usuario supone hacer un clic de ratón en la celda que corresponda, mientras que la modificación del programa ensamblador debe hacerse siguiendo las normas del ensamblador de YASP en la ventana de texto correspondiente.
YASP engloba la totalidad de funcionalidades en una única superficie de trabajo desde donde se ejecutan todos los comandos. Una vez cargada la página se diferencian 7 zonas en la pantalla.
Code. Formada por una caja de texto, en la que se introduce el código que se desea ensamblar para su posterior ejecución en la máquina elemental y dos botones Assemble y Clear , que ensamblan y borran el texto introducido en la caja de texto, respectivamente.
Figura 1.
Detalle de la zona CodeMemory. Contiene un marco en el que se muestra una tabla con el contenido de las 256 posiciones de memoria. Cada fila esta dividida en 4 columnas en las que se puede ver si esta activada o no como punto de ruptura, la posición de memoria en base hexadecimal, el nombre de la etiqueta en el caso de que exista y su contenido.
En la parte inferior hay 4 botones (Quit, Reset, Execute, Step) que permiten interactuar con la máquina elemental.
Figura 2.
Detalle de la zona MemoryRegisters. Compuesta por una tabla donde se pueden ver y modificar el valor de los diferentes registros de la máquina elemental excepto el IR no modificable por el usuario. Cada registro esta representado en binario, hexadecimal, natural, entero y ASCII excepto el IR que se muestra en binario, hexadecimal y como instrucción.
Figura 3. Detalle de la zona Registers Flags.
Permite ver y modificar el contenido de los flags de Carry y Zero.
Figura 4. Detalle de la zona Flags
Display. Formado por una tabla en la que se puede ver el valor, representado en carácter ASCII, de los puertos de salida del 0 al 7.
Figura 5. Detalle de la zona Display
Comunication ports. Formado por una tabla en la que se pueden ver y modificar el valor de los puertos que forman la máquina elemental. Los puertos de entrada muestran el valor en ASCII si es posible y en su defecto en hexadecimal. Los puertos de salida comprendidos entre el 8 y el 15 mantienen el mismo criterio a la hora de mostrarlos por pantalla, en cambio, los puertos de salida comprendidos entre el 0 y el 7 siempre muestra el valor del puerto en base hexadecimal, ya que la representación en carácter ASCII de los mismos esta reservada para el Display.
Figura 6. Detalle de la zona Comunication ports
Acknowledgement/request. Formado por un conjunto de cajas seleccionables que permiten ver y modificar el estado de las 8 líneas de Acknowledgement y ver el estado de las 8 líneas de Request.
Figura 7. Detalle de la zona Acknowledgement/request
Una vez cargado YASP, se muestra toda la información guardada en la máquina elemental, con todos los registros, puertos y posiciones de memoria inicializados a 0. En el caso del registro IR se inicializa con la instrucción JC 0 y la primera posición de memoria con la instrucción HLT, que para la máquina.
Para poder cargar un programa en memoria se escribe el código en la caja de texto Code (Figura 1) para su posterior ensamblaje y ejecución en la máquina elemental. Pulsando el botón Assemble se verifica sintáctica y semánticamente el código introducido, cargándolo en memoria si no hay errores. En el caso de que se haya producido algún error nos informa mediante una alerta. Para borrar el contenido de la caja de texto debemos pulsar el botón Clear que pedirá confirmación.
Una vez cargado el programa ya se puede ejecutar tal y como se explica en la siguiente sección.
El programa se puede ejecutar de dos formas: paso a paso (Step) en la que se ejecuta una instrucción y se detiene o continuada (Execute) en la que se ejecuta instrucción tras instrucción de forma que la máquina solo se detiene cuando se encuentra con un punto de ruptura o con una instrucción de espera (AKL, AKH o HLT). Independientemente del modo de ejecución que utilicemos se destaca en color azul la próxima instrucción a ejecutar, en la memoria y en la caja de texto Code (Figura 8).
Podemos querer ejecutar un programa hasta un punto determinado, puesto que, a veces, es muy tedioso ejecutarlo paso a paso. Con esta finalidad es posible establecer puntos de ruptura, de manera que el programa se va ejecutando hasta encontrar una instrucción marcada.
Figura 8. Ejecución de un programa
Para definir un punto de ruptura hay pulsar con el ratón en la primera celda de cualquiera de las 256 filas de la memoria en la que se quiere parar la máquina. Una vez hecho cambia el color de fondo a rojo. Para eliminarlo simplemente hay que repetir el proceso.
Podemos inicializar la máquina pulsando el botón Reset, que pone a 0 el PC y el IR, con el consiguiente cambio en las ventanas de la memoria y del código.
Debido al espació limitado para mostrar la información, el contenido de las posiciones de memoria solo se muestra según el tipo de la última información que se haya insertado. A pesar de esto hay que tener en cuenta que los valores se pueden dar de 5 formas diferentes:
tipo de dato | prefijo | ejemplo |
decimal sin signo | ninguno | 135 |
decimal con signo | +o- | -15 |
hexadecimal | $ | $FF |
binario | % | %01010001 |
carácter ASCII | ‘ | ‘A |
Tabla 2. Tipos de datos
El programa permite modificar el contenido de la memoria, registros, flags y elementos de comunicación con el exterior. Veamos a continuación como se hace para cada uno de ellos:
Memoria. Para modificar el contenido de una posición hay que pulsar en la cuarta columna de la fila, que corresponde al contenido de la memoria. Mediante una ventana de secuencias de comandos se introduce un valor o una instrucción. Hay que tener en cuenta que en el caso de insertar una instrucción, hay que introducir su mnemónico i sus posibles argumentos. Si la instrucción puede utilizar algún modo de direccionamiento, hay que introducirla en uno de los 4 formatos correctos:
Modo direccionamiento | Especificación | ||
---|---|---|---|
directo | OPCODE | dirección | |
indexado | OPCODE | dirección , X | |
indirecto | OPCODE | * | dirección |
inmediato | OPCODE | # | operando |
Tabla 3. Modos de direccionamiento
Donde hay que sustituir OPCODE por algún mnemónico valido. Hay que tener presente que no existen formas inmediatas para las instrucciones STA, STX i JMP.
Registros. Para modificar el valor de los registros, exceptuando el IR que no es modificable por el usuario, basta con pulsar sobre el botón que identifica a cada uno de ellos e introducir el valor en una de las 5 formas posibles (Tabla 2).
Flags. Los flags de Carry y Zero pueden estar activados o no. Basta con pulsar sobre el botón que los identifica para cambiar al estado contrario.
Comunicación exterior. Como elementos de comunicación con el exterior, se dispone de los puertos y de las líneas de petición/contestación. De los Comunication Ports son modificables los puertos de entrada identificados con los números del 0 al 8 y los puertos de entrada/salida identificados con los números del 8 al 15. Los puertos de salida comprendidos entre el 0 y el 8 solo se modifican cuando se ejecuta la instrucción OUT
En los casos en los que no se pueda determinar cuando hay que realizar una operación de entrada/salida tenemos 8 líneas para realizar peticiones (REQ) y de otras 8 para recibir contestación de conformidad (ACK). Para modificar el estado de una señal ACK hay que pulsar sobre la caja seleccionable correspondiente a la señal que se desea activar o desactivar. Las señales de REQ solo son de lectura, por lo que solo se puede modificar su contenido ejecutando la instrucción RQL
[1] Lluís Ribas Xirgo (2000). Pràctiques de fonaments de computadors. Servei de Publicacions de la Universitat Autònoma de Barcelona. Bellaterra.
[2] David Rodríguez Jurado (2005). jsYASP: Simulador de un procesador docente. Projecte fi de carrera d’Enginyeria Informàtica. UAB. Bellaterra.