Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
DESARROLLO DE ARQUITECTURA TIPO RISC PARA SISTEMAS EMBEBIDOS
GERARDO ALFONSO ROQUE ROMERO
PONTIFICIA UNIVERSIDAD JAVERIANA
FACULTAD DE INGENIERÍA
CARRERA DE INGENIERÍA ELECTRÓNICA
BOGOTÁ, D.C.
2010
2
DESARROLLO DE ARQUITECTURA TIPO RISC PARA SISTEMAS EMBEBIDOS
GERARDO ALFONSO ROQUE ROMERO
Director:
ALEJANDRO FORERO GUZMÁN MSc.
Ingeniero Electrónico
PONTIFICIA UNIVERSIDAD JAVERIANA
FACULTAD DE INGENIERÍA
CARRERA DE INGENIERÍA ELECTRÓNICA
BOGOTÁ, D.C.
2010
Trabajo de grado presentado como
requisito parcial para optar al título
de Ingeniero Electrónico.
3
PONTIFICIA UNIVERSIDAD JAVERIANA
FACULTAD DE INGENIERÍA
CARRERA DE INGENIERÍA ELECTRÓNICA
RECTOR MAGNÍFICO: R.P. JOAQUIN EMILIO SANCHEZ GARCÍA S.J
DECANO ACADÉMICO: Ing. FRANCISCO JAVIER REBOLLEDO MUÑOZ
DECANO DEL MEDIO UNIVERSITARIO: R.P SERGIO BERNAL RESTREPO S.J
DIRECTOR DE CARRERA: Ing. JUAN MANUEL CRUZ BOHÓRQUEZ M, Ed.
DIRECTOR DEL PROYECTO: Ing. ALEJANDRO FORERO GUZMÁN .MSc
4
NOTA DE ADVERTENCIA
“La Universidad no se hace responsable de los conceptos emitidos por algunos de sus alumnos enlos
proyectos de grado. Solo velará porque no se publique nada contrario al dogma y la moral católica y
porque no contengan ataques o polémicas puramente personales. Antes bien, que se vea en ello el
anhelo de buscar la verdad y la justicia.”
Artículo 23 de la Resolución No. 13, del 6 de
julio de 1946, por la cual se reglamenta lo
concerniente a Tesis y Exámenes de Grado en
la Pontificia Universidad Javeriana.
5
AGRADECIMIENTOS
Doy mis agradecimientos a mi familia que siempre estuvo conmigo y me apoyó en todo el proceso,
moral y económicamente. Sin su vital apoyo nada de esto sería posible.
A todos los docentes de la Universidad, a través de los cuales aprendí muchas lecciones, no solo a
nivel académico, sino de vida. Es, en gran parte, gracias a su gran aporte que todo lo que me he
propuesto ha podido lograrse.
Agradezco especialmente al ingeniero Alejandro Forero Guzmán por su apoyo académico, por
permitirme explorar diferentes ideas, siempre con un punto de vista crítico de las cosas. Su
conocimiento ha impulsado gran parte de este documento.
Al grupo de trabajo del laboratorio de electrónica, quienes siempre nos brindaron el mejor servicio.
DEDICATORIAS
Dedico este trabajo de grado a mis padres y mi hermana por su apoyo incondicional y sus consejos
en los momentos más difíciles. A mi pareja, quien ha sido el recurso más valioso que he podido
encontrar en los últimos meses. Su gran aliento ha permitido que todo esto haya sido cumplido y
valorado. También a mis amigos y compañeros de carrera, que han influido en mí de formas que tal
vez no esperaban, y me han hecho crecer como persona. Espero para ellos lo mejor, de mí siempre lo
tendrán.
GERARDO ALFONSO ROQUE ROMERO
6
Tabla de Contenido
1. INTRODUCCIÓN ........................................................................................................................ 8 2. MARCO TEÓRICO ..................................................................................................................... 8 3. ESPECIFICACIONES ................................................................................................................ 10 4. DESARROLLOS ....................................................................................................................... 11
4.1. Diagrama de Bloques General ..................................................................................................... 11 4.1.1. Unidad de Memoria. ................................................................................................................................ 11 4.1.2. Núcleo. .................................................................................................................................................... 12 4.1.3. Bloques de Lógica. .................................................................................................................................. 13 4.1.4. Registros. ................................................................................................................................................. 15
4.2. Conjunto de Instrucciones ............................................................................................................ 16 4.2.1. Formato de Instrucciones......................................................................................................................... 16 4.2.2. Código de Operación y Tipo de Operación ............................................................................................. 18 4.2.3. Conjunto de Instrucciones ....................................................................................................................... 18
5. PRUEBAS DE SIMULACIÓN E IMPLEMENTACIÓN ................................................................. 39 5.1. Simulaciones para el Núcleo ........................................................................................................ 39
5.1.1. Simulación 1 ............................................................................................................................................ 40 5.1.2. Simulación 2 ............................................................................................................................................ 40 5.1.3. Simulación 3 ............................................................................................................................................ 40 5.1.4. Simulacion 4 ............................................................................................................................................ 40
5.2. Simulaciones para el Sistema de Prueba ...................................................................................... 40 5.3. Implementación y Ejecución del Sistema de Prueba en FPGA .................................................... 42
6. ANÁLISIS DE RESULTADOS .................................................................................................... 43 6.1. Desempeño Actual del Sistema ..................................................................................................... 43 6.2. Mejoras en la Unidad de Datos .................................................................................................... 44 6.3. Mejoras en la Unidad de Control. ................................................................................................ 45
7. CONCLUSIONES ...................................................................................................................... 46 8. BIBLIOGRAFÍA ........................................................................................................................ 47 9. ANEXOS................................................................................................................................... 48
9.1. Anexo: ALU .................................................................................................................................. 48 9.1.1. U_ARIT ................................................................................................................................................... 49 9.1.2. U_AND, U_OR, U_NOR ........................................................................................................................ 52 9.1.3. LOGICA_OVERFLOW .......................................................................................................................... 52 9.1.4. SEL_COMPARACION .......................................................................................................................... 53 9.1.5. LOGICA_COMPARACION_CERO ...................................................................................................... 53 9.1.6. ALU_SEL_OUT ..................................................................................................................................... 53
9.2. Anexo: Registros Internos de Propósito General ......................................................................... 54 9.2.1. Estructura de Entrada de Datos................................................................................................................ 54 9.2.2. Estructura de Control ............................................................................................................................... 56 9.2.3. Estructura de Salida de Datos .................................................................................................................. 58
9.3. Anexo: Diagramas de Tiempos ..................................................................................................... 59 9.3.1. Diagrama 1: Operaciones Aritméticas y Lógicas .................................................................................... 59 9.3.2. Diagrama 2: Comparaciones ................................................................................................................... 60 9.3.3. Diagrama 3: Ramificaciones ................................................................................................................... 60 9.3.4. Diagrama 4: Carga de Datos desde Memoria .......................................................................................... 61 9.3.5. Diagrama 5: Almacenamiento de Datos en Memoria .............................................................................. 62 9.3.6. Diagrama 6: Carga de un Dato Inmediato ............................................................................................... 63 9.3.7. Diagrama 7: Habilitación e Inhabilitación de Interrupciones .................................................................. 63 9.3.8. Diagrama 8: Retorno desde Rutina de Interrupción ................................................................................. 64 9.3.9. Diagrama 9: Atención a Solicitud de Interrupción .................................................................................. 64 9.3.10. Diagrama 10: Excepciones ................................................................................................................. 65 9.3.11. Diagrama 11: Reintento de Búsqueda de Instrucción por Código de Operación Ilegal ...................... 66 9.3.12. Diagrama 12: Fetch16 ........................................................................................................................ 67 9.3.13. Diagrama 13: Fetch32 ........................................................................................................................ 68
9.4. Anexo: Máquina de Estados De Unidad de Control .................................................................... 69 9.5. Anexo: Protocolo de Pruebas ....................................................................................................... 78
9.5.1. Esquema del Protocolo ............................................................................................................................ 78
7
9.5.2. Codigo Fuente de Programa de Prueba.................................................................................................... 83 9.6. Anexo: Simulaciones ..................................................................................................................... 83
9.6.1. Núcleo ..................................................................................................................................................... 83 9.6.2. Sistema .................................................................................................................................................... 86
9.7. Anexo: Prueba en FPGA del Sistema ........................................................................................... 88 9.8. Anexo: Código VHDL del Sistema, Distribución del Directorio de VHDL en el Disco Adjunto . 90
8
1. Introducción
En la actualidad, la Facultad de Ingeniería, Departamento de Electrónica, cuenta con el resultado de un
proceso de desarrollo por parte de la Sección de Técnicas Digitales, el cual es un proyecto que consiste
en la arquitectura de un procesador, llamado BINARIC, enfocada al procesamiento y aplicaciones de
propósito general. Por esta razón, es complejo para desarrollar aplicaciones de proyectos de propósito
específico.
Se desarrollará una arquitectura que sea ideada bajo unos criterios completamente diferentes a los de
su antecedente: un núcleo tipo RISC, compacto y sencillo, con enfoque en procesamiento y
aplicaciones de propósito específico. La simplicidad detrás de la arquitectura debe permitir su
implementación en hardware a velocidades de reloj altas. También, debe permitir su completa
integración en un sistema digital embebido más complejo, en los cuales puede manejarse memoria y
periféricos embebidos.
El núcleo debe ser capaz de realizar todas las operaciones de manera eficiente y veloz, haciendo uso
de los recursos del sistema de la mejor manera posible. Este tipo de diseño estará en capacidad de ser
utilizado en la implementación de diferentes configuraciones de sistemas de propósito específico. Esto
es deseable, ya que un único núcleo puede ser la base para la creación de sistemas digitales (sistemas
embebidos con diferentes tipos de periféricos, o con más de un núcleo) que se pueden usar para la
resolución de diferentes problemas en áreas tanto académicas e investigativas, como posiblemente
comerciales.
Por estas razones la arquitectura debe ser simple y escalable. Debe permitir flexibilidad en cuanto a su
interconexión con diferentes sistemas digitales embebidos, y en cuanto a la capacidad que tenga para
la resolución de problemas específicos.
2. Marco Teórico
En lo considerado como arquitecturas convencionales, hoy en día, existen, básicamente, dos divisiones
generales. Por un lado se encuentran las arquitecturas tipo CISC (Complex Instruction-Set Computer),
cuyo diseño se desarrolló en medio de unas condiciones tecnológicas mucho más limitadas que lo que
se encuentra actualmente disponible. El costo de grandes cantidades de memoria, su baja velocidad, la
inexistencia de compiladores, y el costo de implementación de registros llevaron a la industria a
construir arquitecturas con conjuntos de instrucciones complejos, con instrucciones altamente
codificadas, encargadas de muchas labores, y de longitud variable. Se implementaban instrucciones
que hicieran varias tareas a la vez, y que fueran muy robustas por sí mismas. Esto reducía el tiempo
necesario para escribir un programa, bien fuera en código de máquina o en lenguaje ensamblador. Así,
se cambiaba tiempo de codificación de programa por tiempo de ejecución de instrucciones. Sumado a
esto, los procesadores CISC tenían pocos, o 1 solo, registros de propósito general. Así, se diseñaban
instrucciones que operaran sobre la memoria directamente. En cuanto a los modos de
direccionamiento, siempre se intentaba que el Conjunto de Instrucciones fuera lo más ortogonal
posible, con el fin de que, si una instrucción cambiaba en una futura revisión de un dispositivo, no
fuera a afectar al resto del sistema en cuanto al acceso a memoria.[4][5]
A medida que estas condiciones empezaron a cambiar, la industria empezó a replantear muchas de las
normativas generales en las cuales se basaba el diseño de arquitecturas. Liderando esta tendencia, dos
universidades muy importantes de los Estados Unidos presentaron dos propuestas diferentes. Estas
universidades son Stanford y Berkeley. Los proyectos que estas dos instituciones desarrollaron serían
el punto de partida para el cambio de paradigma que ocurrió respecto al desarrollo de
arquitecturas.[1][3][4][5]
9
La universidad de Berkeley llevó a cabo una serie desarrollos y estudios a comienzos de la década de
1980, basándose en la idea de que una escogencia apropiada de un conjunto pequeño de instrucciones,
con modos de direccionamiento limitados, y un adecuado planeamiento de la distribución espacial de
los transistores dentro del chip, podían llevar a la obtención de una arquitectura con alto desempeño.
Además del hardware, el usuario de lenguajes de alto nivel debía poder tener una máquina eficiente.
Así, no solo el hardware debía cambiar, sino también el funcionamiento de los compiladores, pues era
necesario que los comandos del lenguaje de alto nivel fueran traducidos a instrucciones que se
ejecutaran de manera eficiente, y teniendo en cuenta el tamaño del conjunto de instrucciones de la
máquina. Es así como se llega a 2 prototipos, el RISC I, y el RISC II, fabricados en 1981 y 1982
respectivamente.[1]
Al tiempo que la Universidad de Berkeley se dedicaba al desarrollo de sus prototipos, Stanford
también lo estaba haciendo. En el año 1981 se estaba trabajando en el desarrollo del primer procesador
MIPS (“Microprocessor without Interlocked Pipeline Stages”). El concepto más importante detrás de
este procesador era la optimización de ejecución de instrucciones por medio de “pipelining”[4][10],
método en el cual se cargan varias instrucciones en el procesador a la vez, y éste va ejecutándolas de
forma sobrelapada. Además de esto, también se quería que todas las instrucciones se pudieran ejecutar
en un único ciclo de máquina, requerimiento bastante difícil de completar, hasta el punto en que el
primer diseño carecía de instrucciones de multiplicación y división, pues no cumplían el requisito.
Este mismo requisito era el que le daba el nombre de MIPS, pues no era necesario detener ninguna
instancia del “pipeline” a la espera de la ejecución de una instrucción compleja. Otras características
del procesador eran una gran cantidad de registros internos, y pocos modos de direccionamiento, todas
afines con la filosofía RISC.[3][5]
Posteriormente, muchas más empresas desarrolladoras de hardware surgieron, basadas en los
conceptos que traía la filosofía de diseño RISC, impuesta por los dos proyectos antes mencionados.
Así, surgirían arquitecturas RISC de todo tipo y para todo tipo de aplicaciones, desde
supercomputadores y servidores, hasta sistemas embebidos.[2][4][5][6][7]
Bajo el nuevo paradigma RISC se encuentran prácticas de diseño diametralmente opuestas a lo que se
venía haciendo en las arquitecturas CISC: conjuntos de instrucciones con instrucciones que ejecutan
tareas mucho más sencillas, muchos registros internos, y simplificación general de la codificación de
las instrucciones, todas de longitud uniforme. Este tipo de arquitectura permite implementar
optimizaciones sobre las instrucciones que más comúnmente se ejecutan, haciendo que lo hagan en
uno, o unos pocos, ciclos de reloj. Con instrucciones sencillas, se reduce el tiempo de decodificación
de las mismas, haciendo que sea posible tener casos en los cuales una tarea sea ejecutada más
rápidamente por medio de varias instrucciones tipo RISC que con una sola instrucción tipo CISC.
También, la ventaja de tener más registros internos es innegable, pues hacer operaciones sobre el
mismo núcleo es más veloz que hacer operaciones sobre la memoria. Así, el costo de acceder varias
veces a memoria para buscar los operandos de una operación, se justifica por la velocidad de ejecución
de la operación sobre los operandos dentro del procesador (por dar un breve ejemplo). En
consecuencia, las instrucciones de una arquitectura RISC solo operan datos que se encuentren en los
registros internos, mas no directamente en memoria. Por esta misma razón se conoce también como
arquitectura tipo Load-Store, pues solo se accede la memoria para cargar datos desde ella, o para
almacenarlos en ella.[4][5]
Actualmente, la línea que divide las arquitecturas CISC y RISC se está desvaneciendo levemente,
hasta al punto en que arquitecturas modernas tipo CISC se implementan internamente por medio de un
núcleo RISC que convierte cada instrucción CISC en varios pasos tipo RISC. Muchos de los
conceptos RISC se han aplicado con éxito en arquitecturas CISC, y arquitecturas que podrían
considerarse híbridas, por combinar conceptos de ambas filosofías, son lo más común. Así, es posible
hablar de un núcleo que es RISC en algún sentido, más no completamente.[4][5]
10
3. Especificaciones
La arquitectura que se diseña es la de un núcleo tipo RISC, de propósito específico, para su
implementación en sistemas embebidos. Dado el tipo de aplicación a la cual se enfoca, no es un núcleo
complejo. Es, por el contrario, un núcleo sencillo, que debe ser a su vez veloz y versátil.
Es un procesador con 16 registros internos de propósito general de 16 Bits, accesibles al programador.
El núcleo está en capacidad de realizar operaciones aritméticas y lógicas. Dentro de las operaciones
aritméticas, es capaz de realizar sumas y restas, con o sin signo, con datos representados en
complemento a 2. Las operaciones lógicas que realiza son comparaciones entre datos con signo y sin
signo, NOR, AND y OR. Además de esto, está en capacidad de hacer saltos en el flujo de programa,
permitiendo condicionar ejecución de secciones de programas, o de hacer bucles. Es capaz de cargar
datos desde memoria de 16 Bits.
Es capaz de manejar tres diferentes fuentes de interrupción, sin anidamiento. Es decir, cuando se está
atendiendo una interrupción, no es posible atender más interrupciones dentro de la misma, hasta que
ésta no termine. La escogencia de este modo de operación tiene que ver únicamente con cuestiones de
restricción de tiempo y de complejidad del diseño: estructurar un sistema de interrupciones anidadas,
por medio del uso de una pila en memoria y un apuntador interno a dicha pila, le da mucho mejor
tiempo de respuesta al sistema que de la manera como se ha implementado actualmente. Sin embargo,
en vista del enfoque principal del proyecto, cuyos fines son, tanto parte de un diseño compacto como
de un ejercicio académico, es necesario limitar la funcionalidad del mismo a algo que sea realizable y
depurable de manera correcta dentro del plazo de tiempo establecido para su terminación.
Tiene una unidad de control que reconoce 18 diferentes instrucciones, además de tener secuencia de
inicio, atención a interrupciones y atención a excepciones causadas por errores en la operación del
sistema. Todas las instrucciones son de un largo de 16 o 32 Bits, dependiendo de la instrucción. Esto
se realiza con el fin de evitar la carga de una segunda palabra para una instrucción que, por la cantidad
de operandos, no lo requiera. El código de operación de todas las instrucciones ocupa 5 Bits de ancho,
sin embargo existe espacio suficiente en todas las instrucciones para ampliarlo, en caso de futuras
implementaciones de nuevas instrucciones o de ampliación de la arquitectura. Teniendo en cuenta que
el núcleo se diseña con conceptos RISC, la unidad de control debe ser preferiblemente segmentada.
Por cuestiones de tiempo, no es posible lograr esto. Sin embargo, las instrucciones deben estar en
capacidad de adecuarse lo más óptimamente posible a una unidad de control segmentada. Para lograr
esto, es importante que todas las instrucciones se ejecuten en tiempos aproximadamente iguales. Las
instrucciones que más tiempo toman en ejecutarse se demoran cuatro eventos de reloj, la gran mayoría
se demora tres eventos, y unas pocas tardan dos eventos. Para este caso, el balanceo de instrucciones
solo requiere alargar todas las instrucciones a 4 eventos de reloj. Si bien esto disminuye la velocidad
de ejecución de las instrucciones, el mejoramiento a nivel de ejecución de instrucciones por segundo
mejorará, en vista de que este procedimiento se hará en conjunto con la segmentación de la unidad de
control. En vista del enfoque del núcleo, instrucciones complicadas tales como multiplicaciones y
divisiones no han sido implementadas.
La estructura de memoria es tipo Von Neumann. El direccionamiento a la memoria se hace por medio
de los registros internos de 16 Bits. Esto implica que la memoria tiene un tamaño máximo de 2^16
espacios. Todos estos espacios son de 16 Bits de ancho, con el fin de agilizar el tiempo de
almacenamiento y carga de datos e instrucciones desde y hacia el núcleo. Ya que cada instrucción es
de 16 o 32 Bits de ancho, esto significa que cada instrucción ocupa un total de 1 o 2 espacios
contiguos de memoria por instrucción. Las instrucciones no están alineadas, lo que significa que las
instrucciones pueden empezar en cualquier dirección de memoria. La memoria es de carácter veloz y
síncrona, reaccionando, tanto para lectura como escritura, con el borde de subida de la señal de reloj
del sistema. La lectura se realiza en un ciclo de reloj, al igual que la escritura.
El núcleo se espera que pueda ejecutar programas a una velocidad de reloj no inferior a 50MHz. Para
lograr esto, se ha reducido la cantidad de lógica combinatoria a lo mínimo necesario. Además, el
11
sumador de la ALU implementa un sistema de acarreo predictivo para grupos de 4 Bits, lo que permite
la aceleración del cálculo de operaciones aritméticas.
Para implementar físicamente el sistema, se ha escogido el chip FPGA Stratix II, de Altera. Se ha
escogido este chip con el fin de tener espacio suficiente dentro del chip para que todo el sistema quepa
sin tener que hacer concesiones de recursos, tales como el uso completo de los caminos rápidos de
intercomunicación entre macroceldas. También, es el chip adecuado para implementar la memoria
interna en su totalidad (64 KBytes de memoria de 16 Bits de ancho). Además, es un chip que ofrece
velocidades de reloj superiores a 100 MHz, de acuerdo a su hoja de especificaciones, aún para
proyectos con alto uso de recursos[12]. La forma en que se trabajará será por medio de las
herramientas incluidas en el kit de desarrollo de Altera llamado “NIOS II Development Kit, Stratix II
Edition”, del cual puede encontrarse información detallada en la referencia [13].
Para el desarrollo de la descripción en hardware del sistema sobre el chip FPGA escogido, se utilizará
la herramienta de desarrollo de software Quartus II que viene con el kit de desarrollo mencionado. Es
la herramienta más apropiada para trabajar en conjunto con el chip FPGA escogido, pues la compañía
que produce el chip también desarrolla esta herramienta. Esto asegura el aprovechamiento más óptimo
de los recursos del chip FPGA que será utilizado.
4. Desarrollos
El núcleo está compuesto por una serie de sistemas digitales que se interconectan entre sí para lograr
una funcionalidad completa. Es necesario describir todo el sistema, desde un punto de vista general,
para tener una idea de qué es lo que el núcleo es capaz de realizar.
Figura 1. Diagrama de bloques general del sistema.
4.1. Diagrama de Bloques General
El sistema consiste del núcleo, y una unidad de memoria. Esta unidad de memoria tiene la
característica de ser veloz y síncrona, y por tanto no requiere señal de espera de para el núcleo, pues se
asume que responde en un tiempo inferior al que el núcleo espera para usar un dato leído de ella, o
escribir un dato en ella. El tiempo de respuesta de la memoria es menor a medio ciclo de reloj.
4.1.1. Unidad de Memoria.
Se asume que la unidad de memoria contiene tres señales que permiten su control. Estas señales son
ENABLE, READ_ENA, WRITE_ENA, y están representadas por la señal C en la Figura 1. Por medio
de las tres señales se indica a la Unidad de Memoria que se requiere acceso a ella, y además le indica
qué operación debe ejecutarse: lectura o escritura de información.[9] El bus de datos es de 16 Bits, al
igual que el bus de direcciones, lo cual significa que se tiene 64KBytes (1 Byte son 8 Bits para todos
los casos en los que se refiera a esta medida de capacidad) de memoria de 16 Bits disponibles para
instrucciones y datos [8][9]. Por medio del bus de direcciones, se indica a la Unidad de Memoria a qué
posición se requiere acceso, bien sea para lectura o escritura de datos. Este es un bus unidireccional.
Para la escritura de datos se usa el bus BUS_WRITE, y para la lectura de datos se usa el bus
12
BUS_READ. Ambos buses son unidireccionales, de 16 Bits de ancho. Por medio de estos dos buses,
el núcleo y la Unidad de Memoria transfieren información entre sí.
El sistema tiene registros internos de propósito general de 16 Bits. Por esta razón, lo más apropiado es
utilizar una memoria de 16 Bits por posición. Esto permite fácil organización de los datos en memoria.
Para casi todas las operaciones del sistema con la Unidad de Memoria solo se requiere un acceso a
esta. El único caso en el que se requiere acceder a información dos veces en una misma operación es
para la búsqueda de instrucciones, pues estas son de 16 o 32 Bits de ancho.
4.1.2. Núcleo.
Dentro del núcleo se tienen todos los bloques que componen al sistema. El diagrama se muestra a
continuación.
Figura 2. Diagrama de Bloques del núcleo.
En el sistema se muestran 13 bloques diferentes. Todos estos bloques componen al Núcleo. Para cada
bloque existen señales de control, demarcadas en color rojo. Estas señales provienen de la Unidad de
Control, bloque U_CONTROL, pero no se conectan explícitamente a éste para facilitar la lectura del
diagrama. Además, hay señales que van a la Unidad de Control, también de color rojo. Todas las
señales de control se encargan de activar los bloques de forma correcta, de acuerdo a lo que cada
instrucción o acción del núcleo requiera. La señal MASTER_RESET es de 1 Bit, y es externa al
sistema. Cuando su valor es „1‟ lógico, se reinicia la Unidad de Control en el primer paso, además de
reiniciarse el registro SR. Para ver información detallada sobre el funcionamiento lógico de la Unidad
de Control, se puede ver la sección de Anexos 9.4.
13
El sistema carece de un Stack Pointer dedicado para el manejo de una pila. Esto limita la respuesta del
sistema en lo referente a interrupciones. La forma como se manejan las interrupciones es por medio
del almacenamiento de la dirección almacenada en el Program Counter y el contexto del Stack
Pointer en registros con el mismo tamaño que estos, internos al sistema. De esta manera, es posible
mostrar la funcionalidad de las interrupciones, a la vez que se realiza de una forma sencilla y directa.
En una implementación más robusta, se requiere de un registro adicional que apunte a un lugar
específico en memoria durante el inicio del sistema, y que sea usado para almacenar el contexto del
sistema y la dirección de la instrucción a ejecutar posteriormente. Además, el mecanismo de
interrupción debería controlar el almacenamiento y carga de esta información, desde y hacia memoria,
y hacia y desde los registros apropiados.
Como se explicó anteriormente, se implementó un sistema de manejo de interrupciones y excepciones
más sencillo que el descrito en el diseño por cuestiones de tiempo, y para mantener la simplicidad del
diseño, sin quitar al toda la funcionalidad de las interrupciones. Esta es, por tanto, una sección del
núcleo que es expandible para entregarle mayor funcionalidad al mismo. Lo que se requiere es la
adición de un registro que funcione como apuntador a memoria permanentemente, la eliminación de
los registros internos que almacenan el contexto y la dirección de ejecución de la siguiente instrucción,
y por último, la adecuación de las acciones realizadas por el núcleo durante la atención a
interrupciones y excepciones, y el retorno desde estas hacia el flujo normal de programa.
4.1.3. Bloques de Lógica.
4.1.3.1. ALU.
Se encarga de realizar operaciones aritméticas, lógicas y de comparación entre dos operandos. Se
pueden realizar sumas y restas, con datos interpretados como con y sin signo, comparaciones y
operaciones lógicas AND, OR y NOR. Tiene dos señales de entrada de datos: OP1 de 16 Bits, el
primer operando; OP2 de 16 Bits, el segundo operando. Tiene cuatro señales de salida de datos: N de 1
Bit, señal que indica cuando una comparación da negativa o positiva; Z de 1 Bit, que indica cuando
ambos operandos son iguales; ALU_OUT de 16 Bits, que lleva el resultado de la operación
aritmética/lógica que se haya realizado entre las entradas; V de 1 Bit, indica cuando hay un overflow
aritmético a la Unidad de Control. Para más información se puede revisar la sección de Anexo 9.1.
4.1.3.2. OPCODE_DET.
Se encarga de determinar si es necesario realizar la búsqueda de 32 Bits para una instrucción, o solo de
16 Bits. Ya que no todas las instrucciones requieren más de 16 Bits para indicar al núcleo su
comportamiento completo, no es necesario traer los otros 16 Bits, pues no se usarán. Este bloque
determina eso, y mediante su señal de salida indica a la Unidad de Control si debe traer el otro pedazo
de la instrucción o si no se requiere. Tiene una señal de entrada de datos, R_INST_OUT[30] de 1 Bit,
correspondiente al cuarto Bit más significativo del código de operación de la instrucción. Tiene una
señal de salida que va a la Unidad de Control, OPCODE_DET_OUT, de 1 Bit. Por la forma como
están distribuidos los códigos de operación entre todas las instrucciones, para aquellas que usan los 32
Bits, siempre R_INST_OUT[30] = „0‟, y por tanto, la ecuación lógica para este bloque es
OPCODE_DET_OUT = R_INST_OUT[30]‟
4.1.3.3. ILEGAL_OP_DET.
Se encarga de determinar si el código de operación que se está cargando en la instrucción es válido o
no. En caso de ser válido, su valor de salida es 0. Si es inválido, su valor de salida es 1. Tiene una
señal de entrada de datos, BUS_READ[15:11] de 5 Bits, correspondiente a los 5 Bits más
significativos de la instrucción, posición en la que se encuentra el OpCode de la misma. Tiene una
señal de salida que va a la Unidad de Control, ILEGAL_OP_DET_OUT, de 1 Bit. Cuando se intenta
ejecutar una instrucción no válida, la Unidad de Control activa un Bit del registro SR, indicando que
ha habido un error interno, y vuelve a intentar leer la misma instrucción. Si la instrucción es errónea de
14
nuevo, y además ya hubo una lectura previa por error de código ilegal, se genera una excepción. A
continuación se muestra la tabla de verdad que representa la operación de este bloque.
R_INST_OUT[31] R_INST_OUT[30] R_INST_OUT[29] R_INST_OUT[28] R_INST_OUT[27] ILEGAL_OP_DET_OUT
0 0 0 0 0 0
0 0 0 0 1 0
0 0 0 1 0 0
0 0 0 1 1 0
0 0 1 0 0 0
0 0 1 0 1 0
0 0 1 1 0 0
0 0 1 1 1 1
0 1 0 0 0 0
0 1 0 0 1 0
0 1 0 1 0 0
0 1 0 1 1 1
0 1 1 0 0 0
0 1 1 0 1 0
0 1 1 1 0 0
0 1 1 1 1 1
1 0 0 0 0 0
1 0 0 0 1 0
1 0 0 1 0 1
1 0 0 1 1 1
1 0 1 0 0 1
1 0 1 0 1 1
1 0 1 1 0 0
1 0 1 1 1 1
1 1 0 0 0 0
1 1 0 0 1 1
1 1 0 1 0 1
1 1 0 1 1 1
1 1 1 0 0 1
1 1 1 0 1 1
1 1 1 1 0 1
1 1 1 1 1 0
Tabla 1. Tabla de verdad que representa el circuito de lógica combinatoria ILEGAL_OP_DET.
A partir de la Tabla 1, y utilizando mapas de Karnaugh, es posible reducir la tabla a la siguiente
ecuación lógica:
ILEGAL_OP_DET_OUT = D^C‟^B^A + E^D^C‟^A + E‟^C^B^A + E^D^C^A‟ + E^D‟^C^A +
E^C‟^B + E^C^B‟
Donde:
E=BUS_READ[15], D=BUS_READ[14], C=BUS_READ[13], B=BUS_READ[12],
A=BUS_READ[11].
4.1.3.4. MUX_SR_IN.
Multiplexor 2:1, de 5 Bits de ancho, que se encarga de escoger qué señales se almacenan en el registro
SR. Tiene dos señales de entrada de datos: [IA_IN:CT_IN:IE_IN:ALU_SR_IN] de 5 Bits;
SR_C_OUT de 5 Bits. Tiene una señal de salida de datos, SR_IN, de 5 Bits. Tiene una señal de
entrada de control, MUX_SR_IN_C, de 1 Bit, que escoge como señal de salida de datos entre la
primera y la segunda entrada de datos.
4.1.3.5. MUX_R_BANK_IN.
Multiplexor 4:1, de 16 Bits de ancho, que se encarga de escoger qué señal se almacen en el algún
registro del bloque R_BANK. Tiene tres señales de entrada de datos: R_ALU_OUT de 16 Bits;
R_INST_OUT de 16 Bits; BUS_READ de 16 Bits. Tiene una señal de salida de datos, BUS_IN, de 16
Bits. Tiene una señal de entrada de control, MUX_R_BANK_IN_C, de 2 Bit, que escoge como señal
de salida de datos entre las tres entradas de datos.
4.1.3.6. MUX_BUS_DIR_IN.
15
Multiplexor 4:1, de 16 Bits de ancho, que se encarga de escoger qué señal se utiliza como dirección
para la Unidad de Memoria. Tiene cuatro señales de entrada de datos: PC_OUT de 16 Bits;
R_ALU_OUT de 16 Bits; R_IRQ_OUT de 16 Bits; el valor binario fijo 0x0000. Tiene una señal de
salida de datos, BUS_DIR, de 16 Bits. Tiene una señal de entrada de control, MUX_BUS_DIR_IN_C,
de 2 Bit, que escoge como señal de salida de datos entre las cuatro entradas de datos.
4.1.4. Registros.
4.1.4.1. R_BANK.
Representa los 16 registros internos de propósito general del sistema. Todos los registros son de 16
Bits de ancho. Dentro de este banco de registros se tienen 2 registros especiales. Uno de los registros
entrega siempre el valor 0x0000 a su salida, y el otro registro es el Program Counter del sistema.
Tiene cuatro señales de entrada de datos: BUS_IN de 16 Bits; R_INST[3:0] de 4 Bits; R_INST[19:16]
de 4 Bits; R_INST[23:20] de 4 Bits. Tiene tres señales de salida de datos: OP1 de 16 Bits; OP2 de 16
Bits; PC_OUT de 16 Bits. Tiene seis señales de control: MUX_DEC_C de 1 Bits; MUX_PC_C de 3
Bits; PC_C_C de 1 Bit; OP1_SOURCE_C de 1 Bits; W_ENA de 1 Bit; CLK de 1 Bit, el reloj del
sistema. Para más información puede revisarse la sección de Anexos 9.2.
4.1.4.2. R_INST.
Se encarga de recibir y almacenar la instrucción de memoria que se encuentra en la posición indicada
por el Program Counter, para su inmediata decodificación por parte de la Unidad de Control. Sin
embargo, como las instrucciones son de 32 Bits de ancho, y el Bus de Datos es de 16 Bits, es necesario
traer los 4 Bytes de cada instrucción, dos Bytes a la vez. Algunos de estos Bits van a la Unidad de
Control, y otros al bloque R_BANK. Dado que uno de los datos que contiene la instrucción puede ser
un dato inmediato de 16 Bits, es necesario poder sacarlo de la instrucción para enviarlo al registro
interno deseado, y por esto se conectan los Bits R_INST_OUT[19:4] a una de las entradas del bloque
MUX_R_BANK_IN. No todas las instrucciones son de 32 Bits, pues no todas las instrucciones
utilizan más de 16 Bits. Por esto, siempre se trae primero los 16 Bits que contienen el Código de
Operación de la instrucción, para poder determinar si es necesario traer los otros 16 Bits (esto lo
determina el bloque de lógica OPCODE_DET).Tiene una señal de entrada de datos: BUS_READ de
16 Bits, conectada tanto 16 Bits menos significativos como a los 16 Bits más significativos. Tiene una
señal de salida de datos: R_INST_OUT, de 32 Bits. Tiene tres señales de control: R_INST_C_L de 1
Bit, controla la escritura de los 16 Bits menos significativos del registro; R_INST_C_H de 1 Bit,
controla la escritura de los 16 Bits más significativos del registro; CLK de 1 Bit, el reloj del sistema.
4.1.4.3. SR.
También es conocido como Status Register por el papel que cumple. Este registro es de 5 Bits de
ancho, y se encarga de almacenar el estado de diferentes operaciones aritméticas y lógicas que el
núcleo debe realizar. Todos sus Bits tienen nombres: N, Z, IE, CT, IA, correspondientes a SR[0],
SR[1], SR[2], SR[3] y SR[4], respectivamente. N está activo si el resultado de una comparación entre
dos datos genera como resultado que el segundo operando es mayor al primero; Z está activo si un
resultado aritmético es cero; IE es el Bit que indica si las interrupciones están habilitadas o no, para el
núcleo, a nivel general; CT se activa cuando existe un error por ejecución de código de operación
indefinido por primera vez, y se desactiva siempre que se ejecute un código de operación definido; IA
se activa durante una atención a una interrupción o excepción, y se desactiva cuando no se está
atendiendo una, esto con el fin de poder generar una excepción en caso de que el usuario erróneamente
desee salir de una interrupción sin estar dentro de una. Todas las señales de salida van a la Unidad de
Control, con el fin de que ésta pueda revisar los resultados de las operaciones aritméticas y lógicas que
ocurren en la ALU, y ejecutar ramificaciones de acuerdo a esto, además de revisar condiciones
excepcionales. Tiene una señal de entrada de datos: SR_IN de 5 Bits[3]. Tiene una señal de salida de
datos: SR_OUT de 5 Bits. Tiene ocho señales de control: SR_N_C de 1 Bit, controla la escritura del
Bit N del registro; SR_Z_C de 1 Bit, controla la escritura del Bit C del registro; SR_IE_C de 1 Bit,
controla la escritura del Bit IE del registro; SR_CT_C de 1 Bit, controla la escritura del Bit CT del
16
registro; SR_IA_C de 1 Bit, controla la escritura del Bit IA del registro; MASTER_RESET de 1 Bit,
se encarga de reiniciar el valor del registro a „00000‟ cuando el sistema está inicializando; SR_RST,
de 1 Bit, se encarga de reiniciar los primeros cuatro Bits del registro en el valor lógico „0‟ cuando se
empieza la atención a una excepción o interrupción; CLK de 1 Bit, el reloj del sistema.[5]
4.1.4.4. SR_C.
Registro de cinco Bits. Todos sus Bits corresponden a los Bits del bloque SR. Esto es necesario, pues
en caso de que exista una petición para atender una interrupción, o una excepción, el valor de SR se
almacenará en SR_C. Así, todas las salidas de SR son a su vez las entradas de SR_C. Las salidas de
SR_C están conectadas al bloque SR mediante el multiplexor MUX_SR_IN. Tiene una señal de
entrada de datos: SR_OUT de 5 Bits. Tiene una señal de salida de datos: SR_C_OUT de 5 Bits. Tiene
dos señales de control: SR_C_C de 1 Bit, controla la escritura del registro; CLK de 1 Bit, el reloj del
sistema.[5]
4.1.4.5. R_ALU.
Registro de 16 Bits de ancho, que se encarga de almacenar la última operación aritmética/lógica que el
bloque ALU realice. Tiene una señal de entrada de datos: ALU_OUT de 16 Bits. Tiene una señal de
salida de datos: R_ALU_OUT de 16 Bits. Tiene dos señales de control: R_ALU_C de 1 Bit, controla
la escritura del registro; CLK de 1 Bit, el reloj del sistema.
4.1.4.6. R_IRQ.
Registro de 16 Bits de ancho, que contiene la dirección del vector de interrupción que se desea obtener
para atender una interrupción. Este registro tiene la siguiente configuración: R_IRQ[1:0] corresponde
a la entrada, IRQ de 2 Bits , lo que permite el cambio de la dirección del vector de interrupción;
R_IRQ[15:2] están siempre en „0‟, lo que hace que las direcciones de los vectores de interrupción
estén en las direcciones 0x0001, 0x0002 y 0x0003. Este registro actúa como registro de indirección
para rutinas de interrupción, por medio del almacenamiento del vector de interrupción de la rutina que
corresponde al periférico que requiere atención. Tiene una señal de entrada de datos: IRQ de 2 Bits,
contiene un valor entre 0 y 3, en binario. Tiene una señal de salida de datos: R_IRQ_OUT de 16 Bits.
Tiene dos señales de control: R_IRQ_C de 1 Bit, controla la escritura del registro; CLK de 1 Bit, el
reloj del sistema.
4.2. Conjunto de Instrucciones
El núcleo es capaz de realizar una determinada cantidad de operaciones. Estas se llaman instrucciones.
Cada instrucción tiene un determinado formato, que en general consiste en un código de operación,
también llamado OpCode[5][8][10], y los operandos que son objeto de dicha operación. No existe un
único formato para todas las instrucciones, pues dependiendo del tipo de acción, la cantidad y tipo de
operandos cambian.
4.2.1. Formato de Instrucciones
En total, se tienen 18 instrucciones diferentes. Entre estas 18 instrucciones se tiene un total de cinco
diferentes formatos. Existen dos tamaños de instrucción, dependiendo de la cantidad de operandos que
se requieren: instrucciones de 16 Bits y de 32 Bits[5]. Es requerido usar instrucciones de 32 Bits para
poder utilizar instrucciones en formato 3-Address Machine. [5][10]
El código de operación es de 5 Bits para todas las instrucciones, lo que permite que la decodificación
de la instrucción sea más sencilla de implementar. Con solo una excepción, todos los operandos de las
instrucciones son de 4 Bits de tamaño. Esto se debe a que se desea poder direccionar 16 diferentes
registros internos, y para lograrlo es necesaria esa cantidad de Bits para determinar un único registro
de entre todo el conjunto[10]. A continuación se muestra cada uno de los formatos, y se explica con
detalle.
17
4.2.1.1. Formato Tipo A.
Figura 3. Formato de instrucción tipo A, dos operandos y un destino.
El formato de instrucción tipo A se muestra en la Figura 3. Su tamaño es de 32 Bits. Contiene el
código de operación en los cinco Bits más significativos. Tiene dos operandos y un destino.
Operando1 y Operando2 son dos registros internos de propósito general, que corresponden a los
argumentos de entrada de la instrucción. Destino es otro registro interno de propósito general, que
corresponde al argumento de salida de la instrucción. Existen 15 Bits que no se utilizan, y que el
sistema ignora. Así, estos pueden tener cualquier información.
4.2.1.2. Formato Tipo B.
Figura 4. Formato de instrucción tipo B, dos operandos.
El formato de instrucción tipo B se muestra en la Figura 4. Su tamaño es de 16 Bits. Tiene el código
de operación, y dos operandos. Ambos operandos son registros internos de propósito general. No
existe un registro de destino. Existen 3 Bits que no se utilizan, y que el sistema ignora. Así, estos
pueden tener cualquier información.
4.2.1.3. Formato Tipo C.
Figura 5. Formato de instrucción tipo D, un operando inmediato de 16 Bits, y un destino.
El formato de instrucción tipo C se muestra en la Figura 5. Su tamaño es de 32 Bits. Tiene el código
de operación, un operando y un destino. Inmediato16 es un dato inmediato de 16 Bits de ancho, que
viene en la instrucción. Es el argumento de entrada de la instrucción. Destino es un registro interno de
propósito general. Existen 7 Bits que no se utilizan, y que el sistema ignora. Así, estos pueden tener
cualquier información.
4.2.1.4. Formato Tipo D.
18
Figura 6. Formato de instrucción tipo E, sin operandos ni destinos explícitos.
El formato de instrucción tipo D se muestra en la Figura 6. Su tamaño es de 16 Bits. Tiene únicamente
el código de operación, que indica al núcleo lo que se quiere hacer. Las instrucciones que usan este
formato no necesitan argumentos, pues no se hacen operaciones sobre ellos, o porque implícitamente
se conocen. Existen 11 Bits que no se utilizan, y que el sistema ignora. Así, estos pueden tener
cualquier información.
4.2.2. Código de Operación y Tipo de Operación
El código de operación es lo que permite a la Unidad de Control del sistema reconocer la operación
que está almacenada en la memoria. Estas instrucciones son decodificadas y luego, los operandos que
trae la instrucción son utilizados de acuerdo a lo que el código represente.[8]
Para el núcleo, se tienen cinco Bits para el código de operación. A su vez, el código de operación está
subdividido en varios campos que identifican la operación que se requiere hacer, en términos
generales. A continuación se muestran las diferentes operaciones generales que componen el código
de operación de las instrucciones.
4.2.2.1. Operación Aritmética o Lógica.
Todas las operaciones de tipo aritmético entran en este conjunto. Los códigos de operación son de la
forma 00XXX.
4.2.2.2. Operación Comparativa y Otras.
Todas las operaciones de tipo lógico y de comparación entre operandos entran en este conjunto.
Además, tres instrucciones que no entran en los demás grupos entran acá. Los códigos de operación
son de la forma 01XXX.
4.2.2.3. Operación con Memoria.
Todas las operaciones que se comuniquen con memoria entran en este conjunto, además de la
instrucción que maneja carga de datos inmediatos, por ser del mismo tipo. Los códigos de operación
son de la forma 10XXX.
4.2.2.4. Operación de Ramificación.
Todas las operaciones de ramificaciones entran en este conjunto. Los códigos de operación son de la
forma 11XXX.
4.2.3. Conjunto de Instrucciones
A continuación se hace una clasificación de todas las instrucciones que el núcleo es capaz de realizar,
de acuerdo al formato que utilizan. Para cada una de las instrucciones, se explica qué operaciones
realiza[5] y su modo de direccionamiento.
4.2.3.1. Instrucciones con Formato tipo A.
Para este tipo de formato se tienen en total 9 instrucciones diferentes.
4.2.3.1.1. Suma con Signo.
Esta instrucción realiza la suma binaria entre dos registros del conjunto de registros de propósito
general, y devuelve el resultado a un tercer registro. Los dos operandos se interpretan como datos con
signo, en representación de complemento a 2[5][8][10]. El modo de direccionamiento de la instrucción
es Directo, en Registros[5].¡Error! No se encuentra el origen de la referencia. El código de
operación para esta instrucción es 00001. El tipo de instrucción es Aritmética o Lógica. Sus siglas son
SADD.
19
Figura 7. Diagrama de flujo para instrucción SADD.
4.2.3.1.2. Suma sin Signo.
Esta instrucción realiza la suma binaria entre dos registros del conjunto de registros de propósito
general, y devuelve el resultado a un tercer registro. Los dos operandos se interpretan como datos sin
signo. El modo de direccionamiento de la instrucción es Directo, en Registros[5].¡Error! No se
encuentra el origen de la referencia. El código de operación para esta instrucción es 00000. El tipo
de instrucción es Aritmética o Lógica. Sus siglas son UADD.
20
Figura 8. Diagrama de flujo para instrucción UADD.
4.2.3.1.3. Resta con Signo.
Esta instrucción realiza la resta binaria entre dos registros del conjunto de registros de propósito
general, y devuelve el resultado a un tercer registro. Los dos operandos se interpretan como datos con
signo, en representación de complemento a 2[5][8][10]. El modo de direccionamiento de la instrucción
es Directo, en Registros[5].¡Error! No se encuentra el origen de la referencia. El código de
operación para esta instrucción es 00011. El tipo de instrucción es Aritmética o Lógica. Sus siglas son
SSUB.
21
Figura 9. Diagrama de flujo para instrucción SSUB.
4.2.3.1.4. Resta sin Signo.
Esta instrucción realiza la resta binaria entre dos registros del conjunto de registros de propósito
general, y devuelve el resultado a un tercer registro. Los dos operandos se interpretan como datos sin
signo. El modo de direccionamiento de la instrucción es Directo, en Registros[5].¡Error! No se
encuentra el origen de la referencia. El código de operación para esta instrucción es 00010. El tipo
de instrucción es Aritmética o Lógica. Sus siglas son USUB.
22
Figura 10. Diagrama de flujo para instrucción USUB.
4.2.3.1.5. AND Bit a Bit.
Esta instrucción realiza la operación lógica AND, Bit a Bit[4]¡Error! No se encuentra el origen de la
referencia., entre dos registros del conjunto de registros de propósito general, y devuelve el resultado
a un tercer registro. El modo de direccionamiento de la instrucción es Directo, en Registros[5].¡Error!
No se encuentra el origen de la referencia. El código de operación para esta instrucción es 00100. El
tipo de instrucción es Aritmética o Lógica. Sus siglas son AND.
Figura 11. Diagrama de flujo para instrucción AND.
23
4.2.3.1.6. OR Bit a Bit.
Esta instrucción realiza la operación lógica OR, Bit a Bit[4]¡Error! No se encuentra el origen de la
referencia., entre dos registros del conjunto de registros de propósito general, y devuelve el resultado
a un tercer registro. El modo de direccionamiento de la instrucción es Directo, en Registros[5].¡Error!
No se encuentra el origen de la referencia. El código de operación para esta instrucción es 00101. El
tipo de instrucción es Aritmética o Lógica. Sus siglas son OR.
Figura 12. Diagrama de flujo para instrucción OR.
4.2.3.1.7. NOR Bit a Bit.
Esta instrucción realiza la operación OR negada entre dos datos que se encuentran en registros de
propósito general. El resultado de la operación se guarda en el registro indicado por el argumento de
salida de la instrucción. El modo de direccionamiento de la instrucción es Directo, en
Registros[5].¡Error! No se encuentra el origen de la referencia. El código de operación para esta
instrucción es 00110. El tipo de instrucción es Aritmética o Lógica. Sus siglas son NOR.
24
Figura 13. Diagrama de flujo para instrucción NOR.
4.2.3.1.8. Load.
Esta instrucción carga un dato de 16 Bits desde la unidad de memoria del sistema, a uno de los
registros de propósito general. La dirección del dato a cargar corresponde a la suma de los dos
argumentos de entrada de la instrucción, que se encuentran en registros de propósito general. Ambos
datos se interpretan como datos con signo, representados en complemento a 2[5][8][10]. El dato se
carga en el registro indicado por el argumento de salida de la instrucción. El modo de
direccionamiento de la instrucción es de Registro, con Índice[5].¡Error! No se encuentra el origen
de la referencia. El código de operación para esta instrucción es 10000. El tipo de instrucción es Con
Memoria. Sus siglas son LD.
25
Figura 14. Diagrama de flujo para instrucción LD.
4.2.3.1.9. Store.
Esta instrucción almacena un dato de 16 Bits desde uno de los registros de propósito general, hasta la
unidad de memoria del sistema. La dirección del dato a almacenar corresponde a la suma de los dos
argumentos de entrada de la instrucción, que se encuentran en registros de propósito general. Ambos
datos se interpretan como datos con signo, representados en complemento a 2[5][8][10]. El dato a
almacenar se encuentra en el registro indicado por el argumento de salida de la instrucción. El modo
de direccionamiento de la instrucción es de Registro, con Índice[5].¡Error! No se encuentra el origen
de la referencia. El código de operación para esta instrucción es 10001. El tipo de instrucción es Con
Memoria. Sus siglas son STR.
26
Figura 15. Diagrama de flujo para instrucción STR.
4.2.3.2. Instrucciones con Formato tipo B
Para este tipo de formato se tienen en total 4 instrucciones diferentes.
4.2.3.2.1. Comparación con Signo.
Esta instrucción realiza la comparación entre dos registros del conjunto de registros de propósito
general. Se asume que ambos registros tienen datos con signo, en representación de complemento a
2[5][8][10]. El resultado de la comparación aparece en los Bits N y Z del registro de estado. El modo
de direccionamiento de los argumentos de la instrucción es Directo, en Registros[5].¡Error! No se
encuentra el origen de la referencia. El código de operación para esta instrucción es 01001. El tipo
de instrucción es Comparativa y Otras. Sus siglas son SCMP.
27
Figura 16. Diagrama de flujo para instrucción SCMP.
4.2.3.2.2. Comparación sin Signo.
Esta instrucción realiza la comparación entre dos registros del conjunto de registros de propósito
general. Se asume que ambos registros tienen datos sin signo. El resultado de la comparación aparece
en los Bits N y Z del registro de estado. El modo de direccionamiento de la instrucción es Directo, en
Registros[5].¡Error! No se encuentra el origen de la referencia. El código de operación para esta
instrucción es 01000. El tipo de instrucción es Comparativa y Otras. Sus siglas son UCMP.
Figura 17. Diagrama de flujo para instrucción UCMP.
4.2.3.2.3. Ramificación, menor qué.
Esta instrucción cambia el valor del Program Counter, dependiendo de banderas del registro de
estado. Si el valor del Bit N es 0, no se hace nada. De lo contrario, se genera una suma entre el valor
del registro indicado por Operando1 y el valor del registro indicado por Operando2 de la instrucción.
Esta operación trata ambos operandos como datos sin signo, y no genera overflow aritmético. Si se
28
desea hacer una resta, será necesario que el dato en el segundo operando sea el negativo en
complemento a 2 del número que se desea restar[5][8][10]. El modo de direccionamiento de la
instrucción es Directo, en Registros[5].¡Error! No se encuentra el origen de la referencia. El código
de operación para esta instrucción es 11000. El tipo de instrucción es De Ramificación. Sus siglas son
JLT.
Figura 18. Diagrama de flujo para instrucción JLT.
4.2.3.2.4. Ramificación, igual.
Esta instrucción cambia el valor del Program Counter, dependiendo de banderas del registro de
estado. Si el valor del Bit Z es 0, no se hace nada. De lo contrario, se genera una suma entre el valor
del registro indicado por Operando1 y el valor del registro indicado por Operando2 de la instrucción.
Esta operación trata ambos operandos como datos sin signo, y no genera overflow aritmético. Si se
desea hacer una resta, será necesario que el dato en el segundo operando sea el negativo en
complemento a 2 del número que se desea restar[5][8][10]. El modo de direccionamiento de la
instrucción es Directo, en Registros[5].¡Error! No se encuentra el origen de la referencia. El código
de operación para esta instrucción es 11111. El tipo de instrucción es De Ramificación. Sus siglas son
JEQ.
29
Figura 19. Diagrama de flujo para instrucción JEQ.
4.2.3.3. Instrucciones con Formato tipo C
Para este tipo de formato se tiene en total 1 instrucción.
4.2.3.3.1. LoadInmediato16.
Esta instrucción se encarga de cargar un valor inmediato de 16 Bits en cualquiera de los registros de
propósito general. El valor que tenía el registro es reemplazado por el dato inmediato. El modo de
direccionamiento de la instrucción es Inmediato[5].¡Error! No se encuentra el origen de la
referencia. El código de operación para esta instrucción es 10110. El tipo de instrucción es Con
Memoria. Sus siglas son LDI.
30
Figura 20. Diagrama de flujo para instrucción LDI.
4.2.3.4. Instrucciones con Formato tipo D
Para este tipo de formato se tienen en total 4 instrucciones diferentes.
4.2.3.4.1. Habilitar Interrupciones.
Esta instrucción se encarga de cambiar el Bit IE en el registro de estado, al valor 1. Este Bit maneja la
habilitación de interrupciones. No tiene operandos explícitos, y su único operando, el Status Register,
es implícito. Esta instrucción no tiene modo de direccionamiento[5].¡Error! No se encuentra el
origen de la referencia. El código de operación para esta instrucción es 01101. El tipo de instrucción
es Comparativa y Otras. Sus siglas son IENA.
Figura 21. Diagrama de flujo para instrucción IENA.
4.2.3.4.2. Deshabilitar Interrupciones.
Esta instrucción se encarga de cambiar el Bit IE en el registro de estado, al valor 0. Este Bit maneja la
habilitación de interrupciones. No tiene operandos explícitos, y su único operando, el Status Register,
es implícito. Esta instrucción no tiene modo de direccionamiento[5].¡Error! No se encuentra el
31
origen de la referencia. El código de operación para esta instrucción es 01100. El tipo de instrucción
es Comparativa y Otras. Sus siglas son IDIS.
Figura 22. Diagrama de flujo para instrucción IDIS.
4.2.3.4.3. Retorno de Interrupciones.
Esta instrucción restaura el valor del Program Counter y del registro de estado, desde sus respectivos
registros de almacenamiento dedicados. En caso de intentar retornar desde afuera de una interrupción,
se genera una excepción. Esta instrucción no tiene modo de direccionamiento[5].¡Error! No se
encuentra el origen de la referencia. El código de operación para esta instrucción es 01110. El tipo
de instrucción es Comparativa y Otras. Sus siglas son RTI.
32
Figura 23. Diagrama de flujo para instrucción RTI.
4.2.3.4.4. No Operación.
Esta instrucción no hace ninguna acción. En el momento de ser interpretada por la Unidad de Control,
se empieza a realizar el proceso de búsqueda de la siguiente instrucción en memoria. Esta instrucción
no tiene modo de direccionamiento[5]. ¡Error! No se encuentra el origen de la referencia. El código
de operación para esta instrucción es 01010. El tipo de instrucción es Comparativa y Otras. Sus siglas
son NOP.
Figura 24. Diagrama de flujo para instrucción NOP.
33
4.2.3.5. Otras Operaciones
El núcleo es capaz de realizar algunas otras operaciones que no clasifican como instrucciones, pues no
son acciones que el usuario puede programar para utilizar de forma directa. A continuación se habla de
estas operaciones especiales.
4.2.3.5.1. Secuencia de Arranque del Sistema.
Cuando se enciende el sistema, aplicando una fuente de voltaje para su polarización, éste puede estar
en cualquier estado y puede estar realizando cualquier cosa. Por esto, es necesario reiniciarlo. Para
esto, una señal externa debe iniciar todos los Flip Flops del sistema en un valor conocido.
Los registros que lo requieran deben iniciarse. El único registro que requiere iniciarse en un valor
conocido es el Status Register, que se inicia en el valor “00000”. El Program Counter no requiere
iniciar su valor, pues sobre este se carga un valor específico en los primeros pasos de la Unidad de
Control. Los demás registros internos de propósito general no se inician en ningún valor, y se asume
que no contienen información válida. El registro de instrucciones no requiere iniciarse tampoco, pues
va a recibir un valor apropiado en el momento en que la Unidad de Control termine con el proceso de
inicialización[5]¡Error! No se encuentra el origen de la referencia..
La inicialización implica que las interrupciones están deshabilitadas por defecto cuando el núcleo
empieza a funcionar. Esto se hace con el fin de evitar que cualquier tipo de ruido o metaestabilidad
pueda interrumpir al sistema mientras este se estabiliza.
Es necesario poner a la Unidad de Control en su estado inicial. La misma señal externa se encarga de
que los Flip Flops de toda la Unidad de Control estén en los valores apropiados que le permitan
funcionar adecuadamente. Luego, en el primer paso, el núcleo carga desde la dirección de memoria
0x0000 el valor del Program Counter donde se encuentra la primera instrucción, y lo almacena.
Inmediatamente esto ocurre, el sistema empieza la ejecución de instrucciones. Primero debe revisar las
interrupciones. Dado que las interrupciones no están activas al inicio del sistema, éste procederá a
buscar la primera instrucción en memoria. Siempre que la señal externa sea activada, la Unidad de
Control es reiniciada y el sistema arranca de nuevo.
Figura 25. Diagrama de flujo de la secuencia de arranque del sistema.
34
4.2.3.5.2. Búsqueda de Instrucciones en Memoria.
Una vez que el sistema ha iniciado su funcionamiento, debe realizar la búsqueda de la primera
instrucción que ha de ser ejecutada. Esta instrucción viene desde la memoria, y corresponde a la
posición de la misma, apuntada por el Program Counter. Esta instrucción tiene un tamaño de dos o
cuatro Bytes. Para poder almacenar toda la instrucción, es necesario acceder a memoria una o dos
veces, dependiendo de la instrucción. Así, el Program Counter avanza una o dos veces al cargar una
instrucción. Dado que el Program Counter es un registro de 16 Bits[6]¡Error! No se encuentra el
origen de la referencia., no es necesario alinear las instrucciones más allá de lo necesario por cada
espacio de memoria como tal. Es decir, dada la organización de memoria en la que cada posición de
memoria ocupa 16 Bits[5]¡Error! No se encuentra el origen de la referencia., junto con el hecho de
que el Program Counter cuenta dirección por dirección, éste último puede direccionar cualquier
posición de memoria. Así, solo es necesario que las instrucciones estén alineadas a una posición, pues
es la unidad más pequeña de memoria que puede direccionar el Program Counter. Cuando esta
operación empieza, el Program Counter tiene almacenada la dirección de los 16 Bits más
significativos de la instrucción que se busca y que se ejecutará; cuando la operación ha terminado, el
Program Counter tiene almacenada la dirección de los 16 Bits más significativos de la siguiente
instrucción que se debe buscar.
Solo se ejecutará satisfactoriamente una búsqueda de instrucciones si el OpCode de la instrucción
leída existe. De lo contrario, se intentará una lectura de la misma posición de memoria, para confirmar
que no fue un error de lectura de memoria. Si persiste el OpCode ilegal, se genera una excepción, y no
se cargan instrucciones.
35
Figura 26. Diagrama de flujo de la búsqueda de instrucciones en memoria.
4.2.3.5.3. Interrupciones.
Existe un total de tres posibles fuentes externas de interrupción. Las interrupciones se atienden
siempre antes del comienzo del ciclo de búsqueda de instrucciones, de la Unidad de Control. Cuando
una de las posibles fuentes de interrupción está activa, y las interrupciones están habilitadas, el núcleo
copia el valor que tiene en el Program Counter a un registro especial, dedicado para esta tarea, PC_C.
Luego, copia el registro de estado a un registro análogo, SR_C. Después de esto, se reinicia el registro
de estado (banderas de negativo y cero, en el valor lógico 0), y se deshabilitan las interrupciones
(bandera del registro de estado de habilitación de interrupciones en 0). Luego, dependiendo de la
36
fuente de interrupción, se busca en posiciones de memoria desde 0x0001 hasta 0x0003, por la
dirección de memoria que contenga el inicio de la rutina de atención. Es decir, las interrupciones son
vectorizadas, con vectores en las posiciones 0x0001, 0x0002 y 0x0003. El valor encontrado en el
vector es copiado al Program Counter. Si todas las entradas están en cero, no existe interrupción.
Este esquema tiene como consecuencia que solo es posible ingresar a una sola de las rutinas de
interrupción a la vez, y no es posible anidarlas y continuar con la ejecución normal del programa, pues
una segunda interrupción entrando cuando ya se está atendiendo otra, eliminará el estado del núcleo,
previo a la primera interrupción.
Figura 27. Diagrama de flujo de atención a interrupciones.
4.2.3.5.4. Excepciones.
Existen tres fuentes de excepciones internas, no enmascarables. Por un lado está la ejecución de
OpCode ilegal, que ocurre siempre que la Unidad de Control encuentre en una instrucción un código
de operación que no corresponde a ninguna instrucción implementada. Otra ocurre por overflow
aritmético. La tercera ocurre por intentar retornar de una interrupción cuando no se está atendiendo a
una. Todas pueden ocurrir a pesar de tener las interrupciones deshabilitadas. Sin embargo, igual que en
una interrupción, solo es posible atender una a la vez, por lo cual las rutinas de atención a excepciones
deben estar escritas de manera que no generen otra excepción dentro de sí mismas.
En el caso de ejecución de OpCode ilegal, la Unidad de Control se encarga de reintentar la lectura de
la misma posición de memoria desde la cual se leyó el código de operación erróneo, con el fin de
37
confirmar que en efecto este es el valor almacenado en memoria, y no que hubo un error de lectura. A
la vez, el Bit CT en el Status Register se activa, con el fin de indicar que no se ha leído
satisfactoriamente la instrucción y se hará otro intento. En caso de que en la segunda ocasión no se
reconozca el código de operación, se genera una excepción. La Unidad de Control genera una
interrupción de forma interna, almacenando su estado actual en los registros designados para eso, y
mueve el Program Counter a una dirección fija, con el valor 0x0004. Para la atención a la excepción
se apartan diez espacios de memoria contiguos, pues después de este espacio está el espacio para
atención a excepción por overflow aritmético. En este espacio, el usuario se encarga de realizar
cualquier operación que crea conveniente para indicar que hubo un error en la ejecución del programa,
y que debe ser corregido. La detección de OpCode ilegal es indicada con un valor lógico de „1‟ en la
señal ILEGAL_OP_DET_OUT.
Figura 28. Diagrama de flujo de salto a excepción por ejecución de OpCode ilegal.
En el caso de un overflow aritmético, la Unidad de Control hace un salto sobre el Program Counter a
una dirección específica de memoria, por medio del mecanismo de interrupción. La dirección a la cual
salta es 0x000F. Para la atención a la excepción se apartan diez espacios de memoria contiguos, pues
después de este espacio está el espacio para atención a excepción por retorno inválido. Esta excepción
ocurre cuando el Bit de salida del a ALU V está en el valor lógico „1‟ después de una operación
aritmética. La operación aritmética es interrumpida de manera inmediata, y el resultado de la
operación no se almacena.
38
Figura 29. Diagrama de flujo de salto a excepción por overflow aritmético.
En el caso de un retorno de interrupción inválido, la Unidad de Control hace un salto sobre el Program
Counter a una dirección específica de memoria, por medio del mecanismo de interrupción. La
dirección a la cual salta es 0x001A. Por diseño, el usuario no tiene límite de espacio a partir de esta
dirección para las operaciones que deba realizar para atender la excepción. Sin embargo, por ser una
interrupción, está limitando la capacidad de respuesta del sistema si permanece mucho tiempo en esta
situación. Esta excepción ocurre cuando el Bit IA del Status Register está en el valor lógico „0‟ y se
intenta ejecutar una instrucción de salida de interrupción.
39
Figura 30. Diagrama de flujo de salto a excepción por retorno de interrupción inválido.
Para más información sobre las instrucciones (diagramas de tiempos de cada instrucción, y máquina
de estados de la Unidad de Control encargada de manejar las señales para cada instrucción) se puede
revisar las secciones de Anexos 9.3 y 9.4.
5. Pruebas de Simulación e Implementación
5.1. Simulaciones para el Núcleo
Después de haber implementado el sistema en VHDL, utilizando la herramienta de desarrollo Quartus
II de Altera, es necesario comprobar su funcionamiento. En primera instancia, se realiza una prueba
del sistema, sin memorias conectadas. De esta manera, se tiene solo el núcleo (Unidad de Datos y
Control conectadas), y en la simulación solo se reemplazan las entradas al sistema con señales que
corresponden a la manera como la memoria respondería, tanto para ciclos de fetch como para
almacenar y leer datos en esta. Por medio de señales modificadas por el usuario, ubicadas en los
tiempos correctos que el núcleo espera, para cargar instrucciones, datos y almacenar información en
memoria, el sistema funciona correctamente, de acuerdo a lo especificado por cada instrucción (Para
información detallada de cada instrucción ver la sección 4.2, Conjunto de Instrucciones. Para
información detallada del comportamiento en tiempo de las instrucciones, y mapa de transiciones de la
máquina de control del núcleo, ver las secciones de Anexos 9.3 y 9.4.
40
5.1.1. Simulación 1
En esta simulación se ejecutan instrucciones de búsqueda de instrucciones en memoria, carga de datos
inmediatos en los registros internos, almacenamiento de la información de los registros en memoria,
sumas y restas, con y sin signo, operaciones lógicas AND, OR y NOR, carga de datos desde memoria,
y la instrucción NOP. Para más detalles sobre la simulación, revisar la sección de Anexos 9.6.1.1.
5.1.2. Simulación 2
En esta simulación se comprueba el funcionamiento de los saltos condicionales en el flujo de
programa, que el núcleo es capaz de realizar. Se cargan tres datos en los registros internos. R0 siempre
contiene el valor 0x0000, R1 toma el valor 0x8000, R2 toma el valor 0x8000, R3 toma el valor
0x0010. Luego se realizan una serie de comparaciones e intentos de saltos condicionales entre los
registros R0, R1, R2 y R3, corroborando el funcionamiento correcto de los saltos condicionales y las
comparaciones entre datos. Para más detalles sobre la simulación, revisar la sección de Anexos
9.6.1.2.
5.1.3. Simulación 3
En esta simulación se comprueba el funcionamiento de las interrupciones, y de las instrucciones
asociadas. Inicialmente se habilitan las interrupciones con la instrucción IENA, y se comprueba que el
núcleo entra a cada una. Inmediatamente dentro de la interrupción, se sale de esta usando la
instrucción RTI. Luego de probar las tres interrupciones, se deshabilitan usando la instrucción IDIS.
Se comprueba que el núcleo no atiende las interrupciones entrantes, y continua con la ejecución del
programa normal. Para más detalles sobre la simulación, revisar la sección de Anexos 9.6.1.3.
5.1.4. Simulacion 4
En esta simulación se comprueba el funcionamiento de las excepciones, y de las instrucciones
asociadas. El núcleo no puede enmascarar la atención a excepciones. Esto se comprueba nunca
habilitando las interrupciones, y observando que, aún así, el núcleo detiene el flujo normal de
programa para atender las excepciones. Para más detalles sobre la simulación, revisar la sección de
Anexos 9.6.1.4.
De las cuatro simulaciones anteriores, se puede concluir que, tanto la unidad de control como la
unidad de datos del sistema están funcionando correctamente, en conjunto. Todas las operaciones que
realiza el sistema se ejecutan tal y como se especificó en el diseño del mismo, cumpliendo con los
mismos diagramas de tiempos, diagramas de flujos, y máquina de estados.
5.2. Simulaciones para el Sistema de Prueba
Una vez que el funcionamiento del núcleo ha sido validado, ahora es posible simular el programa del
protocolo de pruebas. Para esto, además de programar la memoria del sistema con el código del
programa, es necesario implementar un contador síncrono. El contador se alimenta con el mismo reloj
de todo el sistema. Tiene una señal de salida que corresponde a cuando el contador llega al valor
0x0200, y una señal de entrada que reinicia el conteo a 0x0000. Pues no hace falta contar más allá de
0x0200, es un contador que solo ocupa 10 Bits. Para más detalles sobre el funcionamiento del
contador, puede revisarse la sección de Anexos 9.8.
Teniendo un contador síncrono, solo resta programar la memoria interna del sistema con el programa
que ejecutará la prueba. Este código debe cargarse en memoria en lenguaje de máquina, posición por
posición, pues no se cuenta con herramientas de ensamble ni compilación de código. El esquema de
flujo del programa que se ha implementado para la prueba final del sistema se encuentra en la sección
de Anexos 9.5.1.
41
El código que ha de escribirse sobre la memoria está detallado a manera de lenguaje ensamblador,
para premitir mayor facilidad en su desarrollo, y depuración. El programa implementa el protocolo de
pruebas del sistema, y su código puede verse con detalle en la sección de Anexos 9.5.2.
El sistema, en base a su alta simplicidad, no reserva de manera activa gran cantidad de memoria para
uso específico, más allá de la dirección de inicio de ejecución, las direcciones de atención a
interrupciones y excepciones. Esto permite, y obliga, al usuario, a considerar cuánta memoria
reservará para programas, y cuánta para uso general del programa. El hecho de utilizar una FPGA para
la implementación física del sistema permite simplificar las conexiones de memoria y periféricos, pues
solo es requerido instanciarlos, y describir las interconexiones. Para dar total claridad al
funcionamiento de la prueba realizada, a continuación se muestra un diagrama de la distribución de
memoria para el sistema.
Figura 31. Distribución de memoria del sistema para el protocolo de pruebas.
Como se observa, solo es necesario tener en cuenta algunas pocas direcciones, por diseño. Lo demás
es escogido por el usuario. Para este caso, se escogió un tamaño de 512 espacios de memoria ROM,
que incluye los vectores de interrupción, rutinas de excepción e interrupción, y el programa como tal.
Para la memoria RAM, se escogieron 4096 posiciones, de las cuales las primeras 512 no son
utilizables, ya que para simplificar la implementación, no se desarrolló un controlador de memoria
muy complicado: este consiste en un multiplexor a la entrada del bus de lectura del núcleo, el cual
escoge entre la salida de la ROM o la RAM, dependiendo de la dirección que se esté leyendo.
Para reiniciar el contador, se lee o escribe a la posición de memoria 0x1000. Un bloque de lógica se
encarga de revisar el bus de direcciones del núcleo, de forma que cuando se acceda a esta posición de
memoria, entregue un „1‟ lógico. Este valor lógico se usa para reiniciar el contador.
42
Para poder programar la memoria ROM, utilizando la herramienta de desarrollo Quartus II, se requiere
un archivo con terminación .mif. Este archivo es directamente editable en Quartus II, posición por
posición. De esta manera, se puede escribir el programa en la memoria. Para mirar con detalle el
archivo de inicialización de memoria, se puede consultar la sección de Anexos 9.8.
Ya teniendo un entendimiento completo del protocolo de pruebas y de cómo el sistema debe
cumplirlo, se procede a realizar la simulación del código sobre el sistema. Para realizar la simulación
del sistema y observar en detalle su funcionamiento, puede verse la sección de Anexos 9.6.2.
Tal y como puede verse en la simulación, el sistema recorre las instrucciones del programa tal y como
corresponde, de acuerdo al planteamiento realizado para el protocolo de pruebas del sistema. Las
interrupciones entran en cualquier momento, y el núcleo se encarga de su atención, y luego retorna a la
ejecución normal del programa. Existen saltos a rutinas, con sus respectivos retornos, y todo ocurre
correctamente de acuerdo a lo que se espera, y de acuerdo a las especificaciones del núcleo. Esta
simulación valida, al menos de manera general, el funcionamiento correcto y esperado del núcleo. Lo
que se requiere ahora para su validación formal, es la programación del a FPGA con el sistema
simulado, y la revisión por medio de un analizador de estados lógicos, del funcionamiento del mismo.
5.3. Implementación y Ejecución del Sistema de Prueba en FPGA
Para la implementación del sistema en la FPGA Stratix II, se hizo uso de la tarjeta de desarrollo
incluida en el kit de desarrollo NIOS II Development Kit, Stratix II Edition[13]. Esta tarjeta trae el
chip FPGA Stratix II EP2S60F672C3, y un oscilador externo fijo a una frecuencia de 50MHz [14].
Esto significa que, aún cuando el sistema pueda ejecutarse a una velocidad de reloj superior, en la
práctica las pruebas solo se realizan a la velocidad entregada por el oscilador externo.
Como resultado de la compilación del sistema, se obtuvo una frecuencia de reloj máxima con valores
entre 96MHz y 100MHz, dependiendo de la ubicación del sistema en el chip. Para la realización de la
compilación, se activaron todas las posibles optimizaciones que provee la herramienta de desarrollo
Quartus II, en términos de desempeño del sistema, sacrificando espacio a cambio de obtener mayor
frecuencia de reloj. Se realiza este tipo de optimizaciones sabiendo que el sistema es suficientemente
pequeño como para poder sacrificar espacio por desempeño. Además, la FPGA sobre la que se está
trabajando tiene espacio de sobra para la ubicación del sistema, ocupando menos del 3% de los
elementos lógicos de la misma.
Para la verificación del sistema en tiempo real, sobre la FPGA, se utiliza un analizador de estados
lógicos. La herramienta Quartus II de Altera da la opción de utilizar un analizador de estados lógicos
interno, el cual es instanciado dentro de la FPGA como si fuera parte del proyecto. Para instanciar el
analizador de manera correcta, es necesario compilar el proyecto. Una vez se realiza la compilación
del sistema, se abre la herramienta Signal Tap II [15]. Con esta herramienta es posible observar
cualquier señal interna del sistema, sin necesidad de declararla explícitamente como señal de entrada o
salida para el proyecto, o de tener que exponerla a un pin de entrada/salida del chip. Esto permite
flexibilidad al momento de diseñar, pues no es necesario tener en cuenta el enrutamiento de las señales
que se desea observar, hacia el exterior, además de permitir una velocidad de ejecución en modo de
depuración en tiempo real mayor. Aparte de todo esto, es una herramienta muy sencilla de utilizar, que
no requiere instrumentos externos, ni cables adicionales, aparte de la interfaz JTAG con la cual se
programa la FPGA.
Para este caso, se realizó la visualización de las siguientes señales, que se consideran suficientes para
validar el buen funcionamiento del sistema, a la luz del protocolo de pruebas:
NUCLEO: MASTER_RESET, READ_ENA, WRITE_ENA, IRQ, BUS_READ, BUS_DIR,
BUS_WRITE.
43
R_BANK: W_ENA, PC_OUT, R0_OUT, R1_OUT, R2_OUT, R3_OUT, R4_OUT, R5_OUT,
R6_OUT, R7_OUT, R8_OUT, R9_OUT, R10_OUT, R11_OUT, R12_OUT, R13_OUT, R14_OUT.
Como puede compararse respecto a la simulación, solo se revisan señales que tienen que ver con el
banco de registros. Se considera que no es necesario monitorear más señales porque la gran mayoría
de lo que ocurre en el núcleo tiene que ver con, o se ve reflejado en, los registros internos y el
Program Counter, o se puede ver en el comportamiento del núcleo. Por ejemplo, para verificar que la
ALU está funcionando, se deben tomar dos datos del banco de registros, y el resultado puede
verificarse por medio de lo que el registro destino recibe como resultado de la operación. Lo mismo
ocurre para carga y almacenamiento de datos, y los saltos pueden verificarse con el Program Counter.
La habilitación de interrupciones se ve directamente en el comportamiento del sistema cuando se
recibe una, al igual que las excepciones. Reducir la cantidad de señales para visualizar permite enfocar
la atención solo en lo necesario, además de permitir mayores tiempos de muestreo de señales (los
resultados de muestreo se almacenan en la misma memoria interna de la FPGA, la cual no es
ilimitada).
Una vez escogidas las señales a visualizar, se escogió realizar una captura de datos de las mismas para
un total de 4096 muestras, desde el momento en que se de MASTER_RESET al sistema. Esto permite
ver el funcionamiento del sistema desde el comienzo, por un tiempo igual a (4096-512)*0.2u =
716.8us. La resta de 512 muestras ocurre porque el analizador, en su modo de funcionamiento por
defecto, está constantemente rastreando al sistema, de forma que almacena 512 muestras previas a la
señal de trigger, y 3584 posteriores. Así, los primeros 512 ciclos de reloj no se toman en cuenta. Esto
también permite observar la razón por la cual la simulación termina en 716.8us: de esta forma es
posible comparar los resultados con la simulación, por un tiempo de ejecución igual. Además de esto,
la simulación también se ejecutó con una frecuencia de reloj de 50MHz (período de 0.2us). Para
referencia sobre la observación del resultado del análisis en tiempo real del sistema, se puede ver
información en la sección de Anexos 9.7.
El resultado de la ejecución del sistema en tiempo real, sobre la FPGA, demuestra que el
funcionamiento del sistema es igual a lo esperado. Los resultados obtenidos son, cualitativamente,
iguales a los de la simulación: si bien los archivos generados, uno por la simulación, y el otro por la
ejecución en tiempo real, no son exactamente iguales, como consecuencia de que las interrupciones
entran al sistema en momentos diferentes para cada caso, el comportamiento general del sistema es
igual. Este resultado valida al sistema en términos del correcto funcionamiento del mismo, de acuerdo
a las especificaciones de diseño.
6. Análisis de Resultados
6.1. Desempeño Actual del Sistema
De acuerdo a lo que se ha diseñado, el resultado más evidente es que el sistema funciona de acuerdo a
lo esperado. Todas las instrucciones y demás operaciones se probaron, validando el funcionamiento de
la unidad de control. Además de esto, se ha validado también la interconexión entre la unidad de
control y el resto del sistema, con los datos que entran desde la memoria, que salen a memoria, o que
se procesan dentro del núcleo, siendo manipulados y movidos correctamente.
Respecto a la implementación física del conjunto de instrucciones, se puede decir que el tiempo de
ejecución promedio, en ciclos de reloj, para el conjunto de instrucciones es como se muestra a
continuación.
1. UADD: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
44
2. USUB: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
3. SADD: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
4. SSUB: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
5. AND: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
6. OR: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
7. NOR: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
8. LD: 4 ciclos de fetch, 1 ciclo de decodificación, 3 ciclos de ejecución.
9. STR: 4 ciclos de fetch, 1 ciclo de decodificación, 2 ciclos de ejecución.
10. LDI: 4 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
11. UCMP: 2 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
12. SCMP: 2 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
13. JLT: 2 ciclos de fetch, 1 ciclo de decodificación, 1 o 2 ciclos de ejecución.
14. JEQ: 2 ciclos de fetch, 1 ciclo de decodificación, 1 o 2 ciclos de ejecución.
15. IDIS: 2 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
16. IENA: 2 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
17. RTI: 2 ciclos de fetch, 1 ciclo de decodificación, 1 ciclo de ejecución.
18. NOP: 2 ciclos de fetch, 1 ciclo de decodificación y ejecución.
19. IRQ: 1 ciclo de detección, 3 ciclos de carga de vector.
La cantidad de ciclos por instrucción, en promedio, es de 5.57 Ciclos de reloj por instrucción. En la
práctica, se puede esperar que este número alcance valores cercanos a seis ciclos de reloj por
instrucción, pues la mayoría de instrucciones que se espera que se ejecuten son operaciones
aritméticas y lógicas, tanto para resolución de algoritmos, como para cálculo de direcciones.
Dado que la arquitectura puede correr a una velocidad aproximada de 95MHz, sobre una FPGA Stratix
II, el resultado anterior se puede convertir en 5.57C/i * 0.010526 us/C = 0.058632 us/instrucción =
58.632 ns/instrucción.
Este resultado no es directamente comparable con ningún otro. La razón es que no existe manera de
compilar un programa tipo “benchmark” que permita la comparación entre esta arquitectura respecto
de otras. Sin embargo, es posible afirmar que este desempeño no es el mejor que se puede lograr, con
la arquitectura diseñada. Esto se puede afirmar a través de una serie de diferentes mejoras que pueden
realizarse. A continuación se detallan algunas.
6.2. Mejoras en la Unidad de Datos
La primera y mas evidente mejora que se puede realizar es la de implementar una lógica que maneje
automáticamente el modo de operación de la ALU, a partir del código de operación de la instrucción.
De esta manera, es más fácilmente optimizable la unidad de control, reduciendo instrucciones que
tienen que ver con la ALU (14 de las 18 instrucciones) en una cantidad igual a dos pasos por cada
instrucción.
45
Por otro lado, se puede también implementar una memoria externa con doble puerto. De esta manera,
es posible hacer la lectura de dos posiciones de memoria al mismo tiempo. Esto sería perfecto para las
instrucciones que ocupan dos espacios de memoria, ya que con una sola búsqueda se podrían tener
ambos segmentos de la instrucción. Esto genera un ahorro en la unidad de control de dos pasos para
todas las instrucciones de 32 Bits (10 instrucciones). Una memoria de estas características puede
explotarse aún más por medio del uso de pipelining[10], en casos en que se requiera acceder a dos
posiciones de memoria a la vez, en casos diferentes al descrito.
6.3. Mejoras en la Unidad de Control.
Lo más sencillo que puede realizarse para acelerar el proceso de ejecución de instrucciones es eliminar
las etapas de decodificación explícita, y ubicar la decodificación directamente en el último paso de la
búsqueda de instrucciones. Esto reduce en un ciclo de reloj el tiempo de ejecución de todas las
instrucciones.
Lo siguiente que puede modificarse es el diseño completo de la unidad de control. Se puede usar el
concepto de pipelining[10] para aumentar la cantidad de instrucciones por segundo que se ejecutan. La
segmentación de la unidad de control puede ser sencilla, de tres etapas, de forma que la complejidad
del sistema no aumente demasiado, pero que sí haya un aumento en el rendimiento. Caracterizar la
mejora en el desempeño del sistema después de esta modificación es más complicado.
En total, aplicando todas las optimizaciones, se puede estimar que el núcleo tendría una cantidad de
ciclos por instrucción igual aproximadamente a lo que se muestra a continuación.
1. UADD: 2 ciclos de fetch, 1 ciclo de ejecución.
2. USUB: 2ciclos de fetch, 1 ciclo de ejecución.
3. SADD: 2 ciclos de fetch, 1 ciclo de ejecución.
4. SSUB: 2 ciclos de fetch, 1 ciclo de ejecución.
5. AND: 2 ciclos de fetch, 1 ciclo de ejecución.
6. OR: 2 ciclos de fetch, 1 ciclo de ejecución.
7. NOR: 2 ciclos de fetch, 1 ciclo de ejecución.
8. LD: 2 ciclos de fetch, 2 ciclos de ejecución.
9. STR: 2 ciclos de fetch, 1 ciclos de ejecución.
10. LDI: 2 ciclos de fetch, 1 ciclo de ejecución.
11. UCMP: 2 ciclos de fetch, 1 ciclo de ejecución.
12. SCMP: 2 ciclos de fetch, 1 ciclo de ejecución.
13. JLT: 2 ciclos de fetch, 0 o 1 ciclo de ejecución.
14. JEQ: 2 ciclos de fetch, 0 o 1 ciclo de ejecución.
15. IDIS: 2 ciclos de fetch, 1 ciclo de ejecución.
16. IENA: 2 ciclos de fetch, 1 ciclo de ejecución.
17. RTI: 2 ciclos de fetch, 1 ciclo de ejecución.
46
18. NOP: 2 ciclos de fetch, decodificación y ejecución.
19. IRQ: 1 ciclo de detección, 3 ciclos de carga de vector.
Como se puede ver, todas las instrucciones pueden llegar a tener el mismo tiempo de ejecución,
excepto por la instrucción de carga de datos desde memoria, LD, la cual requiere 1 paso adicional.
Esto entrega un número de ciclos de reloj por instrucción igual a 3.05 ciclos por instrucción.
Asumiendo que la velocidad de reloj máxima no cambiará mucho, se puede hablar de un tiempo de
ejecución de 32.132ns/instrucción, lo cual es casi la mitad de lo que el sistema es capaz de hacer
actualmente. Una unidad de control segmentada puede requerir otro tipo de optimizaciones, por lo cual
evaluar la mejoría del sistema con esta característica es más complicado.
Como consecuencia de todo lo anterior, se puede decir que, aunque los resultados obtenidos por las
simulaciones y la ejecución en tiempo real del sistema validad su funcionamiento de acuerdo a las
especificaciones, el desempeño del sistema puede llegar a ser mucho mejor de lo que es actualmente.
7. Conclusiones
Existen varias áreas del diseño que hay que comentar. Por un lado, se encuentra el manejo de
interrupciones. El sistema es capaz de manejar tres fuentes de interrupción diferentes. Sin embargo, no
existe un mecanismo de anidamiento de interrupciones, y la única forma de atender un conjunto de
varias interrupciones simultáneas es por medio de la atención de una después de la otra. Esto tiene
como consecuencia que el tiempo de atención promedio para las interrupciones puede decrecer. Sin
embargo, en contrapeso a esto, el sistema actualmente cuenta con una manera muy veloz de cargar los
vectores de interrupción: puesto que no se tiene una pila en memoria en la cual se almacenan los
cambios de contexto, y solo se puede tener dos contextos diferentes simultáneamente en un programa
(el del programa principal y el de una interrupción a la vez), esto permite que el almacenamiento del
contexto del programa principal, cuando se recibe una interrupción, pueda almacenarse directamente
en registros internos del sistema, específicamente usados para esa tarea. Al evitar acceso a memoria, se
acelera el proceso de cambio de contexto. Si bien solo es posible manejar una interrupción a la vez, se
ha dejado el mecanismo de tres interrupciones externas, con el fin de dejar la posibilidad de
implementar un mecanismo que las maneje a la vez, sin tener que modificar las entradas al sistema, y
los bloques que manejan datos: solo se requiere un cambio en la unidad de control.
Otro aspecto que debe mirarse con detenimiento es el campo de las posibles optimizaciones que
puedan realizarse al sistema. La primera que debe tenerse en cuenta es incluir lógica combinatoria que
maneje el estado de funcionamiento de la ALU, solo con recibir el código de operaciones de la
instrucción, en cambio de tener que incluir pasos en la unidad de control para el manejo explícito de
ésta. Esto simplifica la unidad de control. No se realizó, pues no fue una opción considerada hasta muy
adelante en el desarrollo de las pruebas del sistema, y su inclusión no era viable en términos del
tiempo utilizado para hacerlo.
El uso de este bloque de lógica abre la posibilidad a una optimización mayor: segmentación de la
unidad de control. Esta posibilidad se exploró inicialmente, dada la natural composición de lo que
constituye un procesador tipo RISC, pero se concluyó que por falta de conocimiento sobre el tema, y
falta de tiempo para obtenerlo y aplicarlo correctamente, no se implementaría. Por medio de todas las
optimizaciones de las que se hablaron en los análisis de resultados, todas las instrucciones del sistema
se ejecutarían en los mismos tiempos, lo cual abre el campo para una implementación de una unidad
de control segmentada mucho más directo y sencillo.
Todas las demás decisiones de diseño que se tomaron, se hicieron a favor de tener un sistema que sea
flexible, pequeño y potencialmente veloz, con instrucciones fácilmente paralelizables. Solo se manejan
datos de un único tamaño (16 Bits), y solo hay reconocimiento limitado e implícito de dos tipos de
datos dentro del núcleo: datos enteros, sin signo, y datos enteros con signo representados en
47
complemento a 2. Esta distinción está implícita en las instrucciones de suma y resta, pues unas asumen
que los datos no tienen signo, y otras sí, conllevando en el segundo caso a una revisión de overflow
aritmético. Las operaciones con datos sin signo están diseñadas para ser usadas para realizar
operaciones con direcciones, donde el signo no existe, y las operaciones con datos con signo están
pensadas para ser usadas en algoritmos aritméticos que requieran manejo de números negativos. El
núcleo como tal no tiene capacidad para distinguir entre un dato con signo y otro sin signo. Es por esto
que el usuario está encargado de utilizar las instrucciones adecuadas para lo que requiera, y de hacer la
distinción por medio de las instrucciones que utilice para el manejo de los datos. Este enfoque ha sido
usado en otras arquitecturas, un ejemplo de las cuales se describe en la referencia [10].
Existen solo dos modos de direccionamiento en el sistema: direccionamiento inmediato, con datos de
16 Bits incluidos dentro de la instrucción, y direccionamiento directo, en registros o en memoria. No
hay operaciones con memoria que impliquen una doble operación: cuando se accede a esta, solo es
para almacenar o cargar datos.
El tamaño de las instrucciones es variable: es de uno o dos espacios de memoria. Inicialmente todas
las instrucciones iban a ser de 32 Bits, pero para la mitad del conjunto de instrucciones no tiene
sentido, pues no ocupan más de 16 Bits. Por medio de esta distinción, es posible acelerar el tiempo de
ejecución de las mismas.
El objetivo general era desarrollar una arquitectura tipo RISC, de propósito específico, para su
posterior implementación en un sistema digital embebido. Dadas las características del conjunto de
instrucciones, es bastante sencillo obtener una máquina digital tipo RISC a partir de éste: un conjunto
de instrucciones pequeño, versátil y que no tiene instrucciones complejas. Además, dado el tamaño del
núcleo, es fácil de implementar en cualquier tipo de sistema embebido: su tamaño lo hace un sistema
que no consume mucha potencia en su operación. Se ha dejado abierta la implementación de la
interfaz con memorias y periféricos, de forma que el usuario pueda realizar un diseño de los mismos
que se adapte a los requerimientos del sistema embebido que se desea implementar. Sin embargo, no
se cumple a cabalidad con el objetivo: el núcleo no tiene una unidad de control segmentada; es
secuencial. Además, existen optimizaciones que no se aplicaron, que generan un conjunto de
instrucciones más uniforme, respecto al tiempo de ejecución de cada instrucción.
En cuanto a los objetivos específicos planteados, las especificaciones técnicas detalladas fueron
generadas. El sistema ha sido detallado en todos los niveles posibles: desde el conjunto de
instrucciones, diagramas de bloques y de interconexión de bloques detallados de cada circuito del
sistema, con su funcionalidad explicada sin ambigüedades, hasta diagramas de tiempos que cada
instrucción debe cumplir, junto con todas las señales que intervienen y su estado en cada momento de
su ejecución, y la máquina de control del sistema, detallada paso a paso por medio del lenguaje AHPL.
También, el sistema fue implementado físicamente en un chip FPGA, y su funcionamiento fue
verificado con respecto a las simulaciones realizadas del mismo. Tanto las pruebas en simulación
como las pruebas sobre el chip FPGA permitieron validar las especificaciones del sistema.
8. Bibliografía
[1] C. H. Séquin, D. A. Patterson. “Design and Implementation of RISC I”. University of Bristol,
England, July 19-30, 1982.
[2] J. Cocke, V. Markstein. “The Evolution of RISC Technology at IBM”. IBM Journal of
Research and Development, Vol. 34, No. 1, 1990.
[3] MIPS Technologies. “MIPS32® Architecture For Programmers, Volume I: Introduction to the
MIPS32®”. Document Number: MD00082, Revision 2.60, June 25, 2008.
48
[4] Samuel O. Aletan. “An Overview of RISC Architecture”. Louisiana Tech University, 1992.
[5] Sivarama P. Dandamudi. “Guide to RISC Processors for Programmers and Engineers”. New
York, USA, 2005.
[6] ARM Limited. “ARM ARM7TDMI-S Reference Manual”. Revision r4p3, 2001.
[7] Compaq Computer Corporation. “The Alpha Architecture Handbook”. Version 4, October
1998.
[8] Francisco Viveros. Apuntes de Clase. Pontificia Universidad Javeriana, Bogotá, Agosto a
Noviembre de 2008.
[9] Luisa García. Apuntes de Clase. Pontificia Universidad Javeriana, Bogotá, Enero a Mayo de
2008.
[10] D. A. Patterson, J. L. Hennessy. “Computer Organization and Design”. San Franciso, USA,
2005, 3rd
Ed.
[11] Altera Corporation. “Internal Memory (RAM and ROM) User Guide”. California, USA,
November 2009. Internet link: http://www.altera.com/literature/ug/ug_ram_rom.pdf, Abril 5,
2010, 01:00 hrs.
[12] Altera Corporation. “Stratix II Device Handbook”. California, USA, 2009. Internet link:
http://www.altera.com/literature/hb/stx2/stratix2_handbook.pdf, Abril 5, 2010, 01:00 hrs.
[13] Altera Corporation. “NIOS II Development Kit, Stratix II Edition”. Internet link:
http://www.altera.com/products/devkits/altera/kit-niosii-2S60.html, Abril 9, 2010, 03:52 hrs.
[14] Altera Corporation. “NIOS Development Board Stratix II Edition Reference Manual”. Internet
link: http://www.altera.com/literature/manual/mnl_nios2_board_stratixII_2s60_rohs.pdf,
Mayo 19, 2010, 23:49 hrs.
[15] Altera Corporation. “Design Debugging Using the SignalTap II Embedded Logic Analyzer”.
Quartus II 9.1 Handbook, Volume 3, Chapter 15. Internet link:
http://www.altera.com/literature/hb/qts/qts_qii53009.pdf, Mayo 7, 2010, 02_39 hrs.
9. Anexos
9.1. Anexo: ALU
La Unidad Aritmética Lógica (ALU, por sus siglas en inglés) es la que se encarga de realizar las
operaciones matemáticas que permiten la ejecución de algoritmos que permitan la resolución de
problemas. Es importante poner atención a los detalles de su implementación, pues de ésta depende
gran parte del desempeño del núcleo. No es posible lograr un buen desempeño si ésta se encuentra en
un nivel inferior respecto a los demás sistemas.
La ALU debe ser capaz de realizar operaciones de suma y resta binarias. También, debe ser capaz de
realizar tres funciones lógicas básicas, AND, OR y NOR, y comparación entre dos operandos. Debe
poder determinar si la suma o resta de dos operandos tratados con signo genera overflow. El tamaño de
datos que debe ser capaz de operar es de 16 Bits. Para realizar la operación de resta, se utiliza
matemática de los números binarios en complemento a 2, así como para la representación de números
negativos[5][8][10]. A continuación se muestra un diagrama de bloques general de la ALU.
49
Figura 32. Diagrama de bloques general de la ALU.
El sistema tiene dos señales de entrada de datos: OP1 de 16 Bits, corresponde al primer operando; OP2
de 16 Bits, corresponde al segundo operando. Tiene cuatro señales de salida de datos: N de 1 Bit,
corresponde al Bit de comparación entre operandos; Z de 1 Bit, corresponde a igualdad entre
operandos: V de 1 Bit, representa overflow aritmético en una suma o resta con instrucciones que tratan
los datos como dígitos con signo en complemento a 2; ALU_OUT de 16 Bits, es el resultado de alguna
de las operaciones que la ALU es capaz de realizar Tiene cuatro señales de control: SEL_OP2_C de 1
Bit; SEL_CARRY_C de 1 Bit; SEL_COMPARACIÓN de 1 Bit; ALU_SEL_OUT_C de 2 Bits. Por
medio de este sistema es posible realizar las diferentes operaciones aritméticas/lógicas de las que se
habló anteriormente.
9.1.1. U_ARIT
A continuación se muestra el bloque U_ARIT con más detalle.
50
Figura 33. Bloque U_ARIT mostrado en detalle.
El bloque U_ARIT recibe dos operandos. Luego, genera su suma o resta binaria, por medio de 16 full
adders en paralelo, y entrega su resultado a la salida. Este resultado será una suma, si la instrucción
escogida es de suma, o una resta. Para realizar la resta, por dentro se tiene un bloque, NEGADOR, que
niega la señal OP2, convirtiéndola en su negativo en complemento a 1. Esta señal luego pasa al
sumador interno, SUMADOR en donde el Bit de acarreo de entrada se pone en 1, con el fin de lograr
que el segundo dato sea negativo, representado en complemento a 2.
El bloque tiene tres salidas, RES_ARIT, que lleva el resultado de la operación aritmética,
CARRY_OUT, que lleva el Bit de acarreo de la operación, y CARRY_14, que en conjunto con la
anterior señal, sirven para que el circuito de detección de overflow funcione.
Dependiendo de la instrucción, la señal de acarreo que entra al bloque SUMADOR es diferente. Si se
requiere hacer sumas, la señal de entrada escogida será el valor lógico „0‟. Si se requiere hacer una
resta, se usa la señal „1‟ para obtener el complemento a 2 del segundo operando.
9.1.1.1. NEGADOR.
Bloque de lógica combinatoria que se encarga de negar su entrada. Tiene una señal de entrada de
datos: OP2 de 16 Bits. Tiene una señal de salida de datos: OP2_NEG de 16 Bits.
9.1.1.2. SEL_OP2.
Multiplexor 2:1 de 16 Bits de ancho. Se encarga de escoger el segundo operando que entra al sumador,
dependiendo de si se realiza una suma o una resta. Tiene dos señales de entrada de datos: OP2 de 16
Bits; OP2_NEG de 16 Bits. Tiene una señal de salida de datos: SEL_OP2_OUT de 16 Bits. Tiene una
señal de control: SEL_OP2_C de 1 Bit, se encarga de escoger cual de las dos entradas de datos estará a
la salida del bloque.
9.1.1.3. SEL_CARRY.
Multiplexor 2:1 de 1 Bit de ancho. Se encarga de escoger la señal de acarreo de entrada para el
sumador, dependiendo de si se realiza una suma o una resta. Tiene dos señales de entrada de datos: el
51
valor binario „0‟; el valor binario „1‟. Tiene una señal de salida de datos: SEL_CARRY_OUT de 1 Bit.
Tiene una señal de control: SEL_CARRY_C de 1 Bit, se encarga de escoger cual de las dos entradas
de datos estará a la salida del bloque.
9.1.1.4. SUMADOR.
Para acelerar el proceso de cálculo de una operación, se implementa un sistema de look-ahead carry,
el cual permite reducir el tiempo que se necesita para calcular el acarreo de los bits más significativos,
dado que ya no es necesario esperar a que éste se propague por toda la lógica, desde el Bit menos
significativo, hasta el más significativo. Sin embargo, no es posible hacer una operación de acarreo
predictivo para todos los 16 Bits, pues la lógica involucrada en el cálculo de acarreos predictivos, a
medida que se calculan Bits más significativos, crece muy rápido. La idea es acelerar el proceso de
cálculo, pero también tener la simplicidad presente. Es por esto que se implementa como se muestra,
con acarreo predictivo para grupos de 4 Bits. Así, se tiene una ganancia en velocidad de cálculo,
manteniendo suficientemente baja la cantidad de compuertas lógicas requeridas para acelerar el
proceso. A continuación se muestra un grupo de cuatro sumadores con acarreo predictivo, y sus
ecuaciones. Las entradas y salidas se representan con las letras A, B y O respectivamente, para evitar
que las ecuaciones extiendan demasiado, solo por los nombres de las variables usadas.[8][10]
Figura 34. Bloque de cuatro sumadores, con acarreo predictivo.
0 0 0 0
1 1 1 1
2 2 2 2
3 3 3 3
( ) ( )
( ) ( )
( ) ( )
( ) ( )
O A xor B xorC
O A xor B xorC
O A xor B xorC
O A xor B xorC
0 0 0
1 1 1
2 2 2
3 3 3
g A B
g A B
g A B
g A B
0 0 0
1 1 1
2 2 2
3 3 3
p A B
p A B
p A B
p A B
52
1 0 0 0
2 1 1 0 1 0 0
3 2 2 1 2 1 0 2 1 0 0
4 3 3 2 3 2 1 3 2 1 0 3 2 1 0 0
( )
( ) ( )
( ) ( ) ( )
( ) ( ) ( ) ( )
C g p C
C g p g p p C
C g p g p p g p p p C
C g p g p p g p p p g p p p p C
Para lograr el sumador de 16 Bits, con cuatro sumadores de cuatro Bits con acarreo predictivo, es
necesario interconectar las líneas de acarreo. La línea de acarreo de salida del primer bloque de cuatro
Bits se conecta directamente a la línea de acarreo de entrada del segundo bloque de cuatro Bits, y así
sucesivamente. La línea de acarreo del cuarto bloque de cuatro Bits es la línea de acarreo de salida del
sumador completo. El bloque SUMADOR, dividido en sus cuatro bloques, se muestra en la Figura 35
a continuación.
Figura 35. Bloque SUMADOR, dividido en 4 bloques que suman 4 Bits, cada uno con acarreo predictivo.
El bloque tiene tres señales de entrada de datos: OP1 de 16 Bits; SEL_OP2_OUT de 16 Bits;
SEL_CARRY_OUT de 1 Bit. Tiene tres señales de salida de datos: RES_ARIT de 16 Bits;
CARRY_14 de 1 Bit; CARRY_OUT de 1 Bit.
9.1.2. U_AND, U_OR, U_NOR
Los bloques de lógica U_AND, U_OR y U_NOR funcionan igual. Para ambos, se tienen dos datos de
entrada, OP1 y OP2. Cada bloque realiza una función lógica entre ambos datos, Bit a Bit, y entrega el
resultado en sus respectivas salidas, RES_AND, RES_OR y RES_NOR, señales de 16 Bits.
9.1.3. LOGICA_OVERFLOW
El bloque LOGICA_OVERFLOW recibe los Bits de acarreo más significativos del bloque U_ARIT.
El bloque se encarga de calcular si existe overflow en la operación aritmética, y entrega el resultado en
53
su salida, V. El resultado del bloque solo se debe tener en cuenta cuando se hacen operaciones
aritméticas entre los datos, tratados con signo[8]. Para saber si una operación entre datos sin signo ha
cometido overflow, se utiliza el Bit de acarreo de la operación[5]. Sin embargo, para las operaciones
que tratan los datos sin signo, la condición de overflow se ignora. La ecuación lógica del bloque
LOGICA_OVERFLOW es ( _ ) ( _14)V CARRY OUY xor CARRY .
Tiene dos señales de entrada de datos: CARRY_14 de 1 Bit; CARRY_OUT de 1 Bit. Tiene una señal
de salida de datos: V de 1 Bit.
9.1.4. SEL_COMPARACION
Para lograr la comparación entre dos datos, es necesario saber de antemano si los datos son tratados
como con signo o sin signo, pues dependiendo de esto, la forma de detectar un resultado para la
comparación cambia. Para esto, se necesitan 2 bloques diferentes, uno que escoja el resultado de la
comparación dependiendo de si los datos son tratados con signo o sin signo, y otro bloque que
compare si ambos datos son iguales o no (en este caso no interesa el signo, pues si son iguales, deben
ser iguales Bit a Bit). Estos bloques se encargan de actualizar los Bits del Status Register que indican
si una operación dio cero, o si un número es mayor que otro.
El bloque SEL_COMPARACION escoge entre las dos posibles formas de detectar si un dato es mayor
o menor que otro. Si los datos se tratan con signo, este bloque escoge el Bit más significativo de la
resta de los datos, RES_ARIT[15],que corresponde al signo. Así, si la resta es positiva, N será 0 y OP1
es mayor a OP2. Si la resta es negativa, N será 1 y OP1 es menor que OP2. Por otro lado, si los datos
son tratados sin signo, el bloque escoge el Bit de acarreo de salida del bloque U_ARIT. La razón para
esto es que si ambos datos son tratados sin signo, la única forma de saber si OP1 es menor que OP2, es
si la resta genera un oveflow. Dado que para datos sin signo, el Bit de acarreo se usa para detectar
overflow, es esta la forma más sencilla de detectar si un número es mayor que otro. Así, si OP1 es
mayor que OP2, no habrá overflow y CARRY_OUT será 0, haciendo que N sea 0. Si OP1 es menor
que OP2, sí habrá overflow, CARRY_OUT será 1, y N será 1. Tiene dos señales de entrada de datos:
CARRY_OUT de 1 Bit; RES_ARIT[15] de 1 Bit. Tiene una señal de salida de datos: N de 1 Bit.
Tiene una señal de control: SEL_COMPARACION_C de 1 Bits, se encarga de escoger cual resultado
de las dos posibles formas de comparación va a la salida.
9.1.5. LOGICA_COMPARACION_CERO
El bloque LOGICA_COMPARACION_CERO se encarga de detectar igualdad entre dos datos. La
forma en que lo hace es por medio del resultado de la resta entre dos datos. Cuando los datos son
iguales, la entrada será igual a 0x0000. Así, para poder detectar igualdad, se usa la siguiente ecuación
en este bloque:
_ [15]' _ [14]' _ [1]' _ [0]'Z RES ARIT RES ARIT RES ARIT RES ARIT
Tiene una señal de entrada de datos: RES_ARIT de 16 Bits. Tiene una señal de salida de datos: Z de 1
Bit.
9.1.6. ALU_SEL_OUT
Este bloque es un multiplexor 4:1 de 16 Bits de ancho. Tiene cuatro señales de entrada de datos:
RES_ARIT de 16 Bits; RES_AND de 16 Bits; RES_OR de 16 Bits; RES_NOR de 16 Bits. Tiene una
señal de salida de datos: ALU_OUT de 16 Bits. Tiene una señal de control: ALU_SEL_OUT_C de 2
Bits, se encarga de dejar pasar una sola de las cuatro entradas de datos, a la señal de salida de la ALU.
54
9.2. Anexo: Registros Internos de Propósito General
El núcleo debe tener un conjunto de bloques que le permitan almacenar información de forma
temporal para poder realizar diferentes funciones, almacenar resultados o determinar el estado de
algunos subsistemas. A continuación se describirán todos los registros internos de propósito general
que tiene el sistema.[9]
9.2.1. Estructura de Entrada de Datos
En el diagrama en bloques del sistema se describió de forma general todos los bloques que tiene el
núcleo. Entre estos se incluyen los registros. Sin embargo, no se dio una descripción detallada del
conjunto de registros de propósito general, R_BANK. Para empezar, todos los registros están hechos a
partir de Flip Flops del tipo Data. No es necesario utilizar Flip Flops más complejos (tales como los J-
K) pues con los Data es posible realizar todo lo que se necesita, sin que sea necesaria circuitería digital
adicional. Esto además debe mejorar el tiempo de respuesta de cada Flip Flop[9]. Por último, todos
deben tener señal de habilitación, que les permita recibir información solo en el momento en que sea
necesario, e ignorarla cuando no. A continuación se muestra un diagrama de bloques del sistema de
registros internos de propósito general R_BANK. Por simplicidad, en la Figura 36 solo se muestran las
señales que portan datos. Las señales de control se muestran más adelante.
Figura 36. Diagrama de bloques general del conjunto de registros internos de propósito general.
9.2.1.1. R0 a R15 y PC_C.
Como se puede ver, hay un total de 16 registros internos de propósito general, todos de 16 Bits de
ancho. De estos, 14 tienen exactamente la misma estructura. Tienen todos en común la misma señal de
entrada de datos: BUS_IN de 16 Bits. Tienen cada uno una señal de salida de datos independiente:
Rn_OUT de 16 Bits, donde la “n” representa un número entre 1 y 14.
Existen dos registros que pueden ser tratados como de propósito general, pero que en realidad
funcionan para tareas específicas. El registro R15 es el Program Counter, registro encargado de
mantener la dirección de memoria de la instrucción actual de ejecución. Tiene un sumador dedicado a
incrementar su valor en 1 o 2 después de la búsqueda de una instrucción en memoria, un multiplexor a
su entrada, y un registro de almacenamiento temporal de su dato. Tiene una señal de entrada:
MUX_PC_OUT de 16 Bits. Tiene una señal de salida: PC_OUT de 16 Bits.
55
Hay además un registro que no es directamente direccionable: PC_C funciona cuando entra una
solicitud de atención a interrupción o excepción. Su función es la de guardar el dato almacenado por el
registro R15 de forma temporal, mientras que la interrupción es atendida. Luego, cuando se termina la
atención a la misma, R15 toma el valor almacenado en PC_C y continúa con el flujo normal del
programa. El registro PC_C tiene una señal de entrada de datos: PC_OUT de 16 Bits. Tiene una señal
de salida de datos: PC_C_OUT de 16 Bits. Tiene dos señales de control: PC_C_C, de 1 Bit; CLK, de 1
Bit, el reloj del sistema.
En la Figura 37 se muestra el diagrama general de todos los registros, excepto por R0. Este es un
registro interno de propósito general, pero no funciona como tal. Es una señal de salida, más no sirve
para almacenar información. Las salidas del banco de registros que referencian al registro R0 están
conectadas al valor 0x0000. Esto permite hacer diferentes operaciones que requieren de este valor, sin
necesidad de cargarlo explícitamente a un registro. Además, permite una forma rápida para copiar un
registro a otro, simplemente efectuando una suma del dato a copiar contra este registro, y almacenando
el resultado en otro. Tratar de escribir datos a este registro no tendrá ningún efecto en su valor.
Figura 37. Diagrama de bloques detallado de los registros R1 a R15.
9.2.1.2. PC_ADD.
Es un sumador dedicado específicamente a aumentar en 1 el valor del registro R15, cuando sea
necesario (después de la búsqueda de instrucciones en memoria). Se implementa por medio de 16 half-
adders en cascada, de la forma que se muestra en la Figura 38. Las entradas de cada bloque sumador
son X e Y, S corresponde a la salida, y C al bit de acarreo generado. Las entradas y salidas están
relacionadas por medio de las siguientes ecuaciones lógicas:
S = X xor Y
C = X ^ Y
El último sumador de la cadena no requiere tener señal de acarreo de salida. El retraso en el cálculo de
último Bit será de 16 veces el tiempo de propagación de una compuerta AND, pues esta es la cantidad
de compuertas que hay entre el primer acarreo generado y el último. Tiene una señal de entrada de
datos: PC_OUT de 16 Bits. Tiene una señal de salida de datos: PC_ADD_OUT de 16 Bits.
56
Figura 38. Diagrama Diagrama de bloques de PC_ADD.
9.2.1.3. MUX_PC.
La entrada al registro R15 está controlada por un multiplexor 8:1, que escoge una de diferentes
posibles entradas. Tiene seis entradas de datos: BUS_IN de 16 Bits, que corresponde a cuando se trata
el registro R15 como de propósito general; PC_ADD_OUT de 16 Bits, usada cuando es necesario
aumentar el valor del Program Counter; PC_C_OUT de 16 Bits, usada cuando se hace el retorno de
una interrupción o excepción; el valor 0x0004, que corresponde a la dirección de inicio de la rutina de
atención a la excepción causada por la ejecución de código de operación no definido; el valor 0x000F,
que corresponde a la dirección de inicio de la rutina de atención a la excepción causada por overflow
aritmético; el valor 0x001A, que corresponde a la dirección de inicio de la rutina de atención a la
excepción causada por intentar retornar de una interrupción cuando no se está dentro de una. Tiene
una señal de salida de datos: MUX_PC_OUT de 16 Bits. Tiene una señal de control: MUX_PC_C de
3 Bits, que se encarga de controlar cual de las entradas de datos se refleja a la salida.
9.2.2. Estructura de Control
9.2.2.1. BANK_DEC.
Para lograr almacenar la información en algún registro especificado por una instrucción, es necesario
poder habilitarlo de forma independiente. Para esto es necesario tener un decodificador, cuya entrada
será un número codificado en binario de 4 Bits, entre 0 y 15, y cuya salida serán 16 bits, cada uno
conectado a un registro. Además de esto, puesto que no siempre se requiere almacenar información en
algún registro, se hace una AND para cada registro, entre la señal que lo selecciona desde el
decodificador, y una señal que es la misma para todos los registros, W_ENA. Así, cuando W_ENA
esté en un alto lógico, y se esté seleccionando un registro por medio del decodificador, dicho registro
será escrito con el valor que se encuentre en el bus BUS_IN. Si alguna de las dos señales de control
está en un bajo lógico, el registro no será escrito.[10] La señal de salida del decodificador
57
correspondiente al registro R0 no está conectada a nada, pues el registro como tal no existe. El
decodificador tiene una señal de entrada de datos: MUX_DEC_OUT de 4 Bits. Tiene una señal de
salida de datos: BANK_DEC_OUT de 16 Bits.
9.2.2.2. MUX_DEC.
Algunas veces es necesario escribir específicamente el registro R15. Para lograr esto, se usa un
multiplexor 2:1 de 4 Bits, a la entrada del decodificador. Una entrada corresponde al registro indicado
por la instrucción. La otra entrada será el valor binario que indica que debe activarse el registro R15
para ese caso. Tiene dos señales de entrada de datos: R_INST[3:0] de 4 Bits; el valor binario „1111‟.
Tiene una señal de salida de datos: MUX_DEC_OUT de 4 Bits. Tiene una señal de control:
MUX_DEC_C de 2 Bits, que controla cual de las dos entradas se verá reflejada en la salida.
9.2.2.3. Control al registro PC_C.
El registro PC_C solo se activa en un caso específico, controlable fácilmente por medio de la Unidad
de Control con la señal PC_C_C, y por ende no requiere acceso por medio de la señal de escritura o de
alguna señal del decodificador. Además, no es un registro direccionable por el usuario.
Para lograr entender completamente su funcionamiento, se presenta un diagrama de bloques de las
señales de control en la Figura 39. Todas las señales de datos se omiten, pues ya han sido mostradas
anteriormente, además su inclusión complica el diagrama, impidiendo tener mayor claridad sobre su
funcionamiento.
Figura 39. Diagrama de bloques del esquema de control de habilitación del banco de registros.
58
El banco de registros internos de propósito general funciona de forma similar a como funciona una
unidad de memoria. Así, es necesario decodificar qué registro quiere cargarse con información, para
así poder aplicar la señal apropiada solo a ese registro.
Figura 40. Conexiones necesarias para escoger registros de salida del banco de registros.
9.2.3. Estructura de Salida de Datos
Lo último que hay que tener en cuenta es la selección de salidas para el banco de registros. La señal de
16x16 Bits, BUS_OUT contiene las 16 salidas de los 16 registros, cada una de 16 Bits. Sin embargo,
el banco de registros tiene 3 salidas diferentes. Dos de estas salidas pueden corresponder a cualquier
registro interno, mientras que la tercera salida es exclusivamente usada por el registro R15 para la
búsqueda de instrucciones en memoria.
9.2.3.1. MUX1 y MUX2.
Para lograr la selección de qué registro se desea llevar a las salidas, se utilizan dos multiplexores 16:1
de 16 Bits de ancho. Estos multiplexores conectan los registros de salida escogidos a la ALU para su
operación. El multiplexor MUX1, además lleva la salida escogida al registro R_OUT, cuando se desea
almacenar un dato en memoria. En la Figura 40 se muestra la configuración del banco de registros con
los dos multiplexores, y la salida del registro R15. Ambos multiplexores tienen la misma señal de
entrada de datos: BUS_OUT de 16x16 Bits. Tienen, respectivamente, estas señales de salida de datos:
OP1 de 16 Bits, para MUX1; OP2 de 16 Bits, para MUX2. Tienen, respectivamente, estas señales de
control: MUX_OP1_OUT de 4 Bits, para MUX1; R_INST[19:16] de 4 Bits, para MUX2.
9.2.3.2. MUX_OP1.
La señal de control que va al multiplexor MUX1 depende de la instrucción. Para controlar cuál de
estos dos valores corresponde a la salida, se utiliza otro multiplexor, conectado a la señal de control de
MUX1. Este es un multiplexor 2:1 de 4 Bits. Tiene dos señales de entrada de datos: R_INST[3:0] de 4
Bits; R_INST[23:20] de 4 Bits. Tiene una señal de salida de datos: MUX_OP1_OUT de 4 Bits. Tiene
una señal de control: OP1_SOURCE_C de 1 Bit, que controla cual de las dos señales de entrada se
refleja a la salida, indicando cual de las dos posibles opciones corresponde al registro que se desea
escoger como registro de salida.
59
9.3. Anexo: Diagramas de Tiempos
Es posible clasificar las instrucciones de dos maneras: por formato de instrucción, o por tipo de
operación que realiza. Para tener una idea general del comportamiento de las instrucciones, es
importante tener en cuenta la segunda organización.
Existen, en general, cinco tipos de instrucciones que el núcleo es capaz de realizar. Estas son:
instrucciones aritméticas, lógicas, de ramificación, con memoria, y otras que no pertenecen a ninguna
categoría. Sin embargo, como se verá más adelante, esta división es muy general, y hay algunas
instrucciones que, a pesar de caer dentro de una de las anteriores categorías, ejecutan acciones
suficientemente diferentes como para poder enmarcarlas en un solo tipo de instrucción. Por esta razón,
es necesario realizar más diagramas de tiempos de los que se podría esperar.
A continuación se muestran los diferentes diagramas de tiempos que cubren todas las instrucciones.
Para cada diagrama se especifica qué instrucciones cubre, aclaraciones que sea necesario realizar, y
solo se incluirán las señales que tienen importancia directa para la instrucción. Los nombres de las
señales usados son iguales que los que se encuentran en la descripción del sistema en bloques (ver
secciones 4.1, 9.1, 9.2).
9.3.1. Diagrama 1: Operaciones Aritméticas y Lógicas
El diagrama de tiempos mostrado en la Figura 41 incluye las siguientes instrucciones: Resta sin Signo;
Resta con Signo; Suma sin Signo; Suma con Signo; AND; OR; NOR. Dependiendo de la operación, la
señal SR_C_C estará activa durante el segundo evento, o no. Así mismo, dependiendo de la operación,
será el valor de los datos D_CARRY y D_OPERACION. En caso de que el registro de destino sea
R15, es necesario escoger como entrada el bus de entrada al banco de registros. El valor del dato
D_OP2 depende de si la instrucción es suma o resta. Para las operaciones lógicas, el valor de los datos
D_CARRY y D_OP2 no importa.
60
Figura 41. Diagrama de Tiempos para instrucciones USUB, SSUB, UADD, SADD, AND, OR, NOR.
9.3.2. Diagrama 2: Comparaciones
El diagrama de tiempos mostrado en la Figura 42 incluye las siguientes instrucciones: Comparación
con Signo; Comparación sin Signo. Dependiendo del resultado de la comparación, y del tipo de
comparación serán los valores que tenga el registro SR en sus Bits 0 y 1, así como el valor que tenga la
señal SEL_COMPARACION_C.
Figura 42. Diagrama de Tiempos para instrucciones: UCMP, SCMP.
9.3.3. Diagrama 3: Ramificaciones
El diagrama de tiempos mostrado en la Figura 43 incluye las siguientes instrucciones: Ramificación,
Menor qué; Ramificación, Igual qué.
Es importante aclarar que durante el primer ciclo de las instrucciones la Unidad de Control toma la
decisión de ejecutar la ramificación o no. Sin embargo, se aprovecha para realizar la suma entre los
operandos necesarios, con el fin de ahorrar tiempo.
61
Figura 43. Diagrama de Tiempos para instrucciones: JLT, JEQ.
9.3.4. Diagrama 4: Carga de Datos desde Memoria
El diagrama de tiempos mostrado en la Figura 44 incluye las siguientes instrucciones: Load. El cálculo
de la dirección se realiza por medio de la suma de los dos operandos. Los datos se tratan sin signo.
Además, se asume que la unidad de memoria reacciona en un tiempo igual o inferior a medio ciclo de
reloj.
62
Figura 44. Diagrama de Tiempos para instrucciones: LD.
9.3.5. Diagrama 5: Almacenamiento de Datos en Memoria
El diagrama de tiempos mostrado en la Figura 45 incluye las siguientes instrucciones: Store. El cálculo
de la dirección se realiza por medio de la suma de los dos operandos. Los datos se tratan sin signo.
Además, se asume que la unidad de memoria reacciona en un tiempo igual o inferior a medio ciclo de
reloj.
63
Figura 45. Diagrama de Tiempos para instrucciones: STR.
9.3.6. Diagrama 6: Carga de un Dato Inmediato
El diagrama de tiempos mostrado en la Figura 46 incluye las siguientes instrucciones: Load
Inmmediato16.
Figura 46. Diagrama de Tiempos para instrucciones: LDI.
9.3.7. Diagrama 7: Habilitación e Inhabilitación de Interrupciones
El diagrama de tiempos mostrado en la Figura 47 incluye las siguientes instrucciones: Habilitación de
Interrupciones; Inhabilitación de Interrupciones.
El valor que representa la señal D_IE dentro del diagrama corresponde a un 0 o 1 lógico, dependiendo
de si se está inhabilitando o habilitando la capacidad de atender interrupciones del núcleo.
64
Figura 47. Diagrama de Tiempos para instrucciones: IENA, IDIS.
9.3.8. Diagrama 8: Retorno desde Rutina de Interrupción
El diagrama de tiempos mostrado en la Figura 48 incluye las siguientes instrucciones: Retorno de
Interrupción.
Figura 48. Diagrama de Tiempos para instrucciones: RTI.
9.3.9. Diagrama 9: Atención a Solicitud de Interrupción
El diagrama de tiempos mostrado en la Figura 49 incluye las siguientes operaciones: Atención a
Interrupción. Los valores de N, Z y CT corresponden a lo que esté almacenado en el registro status
register en los Bits SR_OUT[0], SR_OUT[1] y SR_OUT[3] en el momento en que entra la
interrupción. El Bit SR_OUT[2] obligatoriamente debe tener el valor lógico „1‟, indicando que las
interrupciones están habilitadas. El Bit SR_OUT[4] inicialmente debe tener el valor lógico „0‟, pues
no se puede estar atendiendo ninguna interrupción antes de la que entra. Cuando se entra a la
excepción, se almacena el valor lógico „1‟ en él, indicando que se está dentro de una excepción.
65
Figura 49. Diagrama de Tiempos para operación: IRQ.
9.3.10. Diagrama 10: Excepciones
El diagrama de tiempos mostrado en la Figura 50 incluye las siguientes operaciones: Excepción por
ejecución de OpCode ilegal; Excepción por overflow aritmético; Excepción por retorno inválido de
interrupción. El valor del dato EXCP_SEL será “011”, “100” o “101”, dependiendo respectivamente
de la excepción. De igual forma, el valor del dato EXCP_VECTOR será 0x0004, 0x000F o 0x001A.
Los valores de N, Z, IE CT corresponden a lo que esté almacenado en el registro status register en los
Bits SR_OUT[0:3] en el momento en que entra la interrupción. No interesa si las interrupciones están
habilitadas o no, pues una excepción es una situación más grave y que el usuario debe tener siempre
en cuenta cuando ocurre. El Bit SR_OUT[4] inicialmente debe tener el valor lógico „0‟, pues no se
puede estar atendiendo ninguna interrupción antes de la que entra. Cuando se entra a la excepción, se
almacena el valor lógico „1‟ en él, indicando que se está dentro de una excepción.
66
Figura 50. Diagrama de Tiempos para operación: Excepción por OpCode ilegal, Excepción por overflow aritmético, Excepción por
salida inválida de interrupción.
9.3.11. Diagrama 11: Reintento de Búsqueda de Instrucción por Código
de Operación Ilegal
El diagrama de tiempos mostrado en la Figura 51 incluye las siguientes operaciones: Reintento de
búsqueda de instrucción por OpCode ilegal. El valor que tenga la señal ILEGAL_OP_DET_OUT
cuando haya un dato válido sobre el bus de datos indica a la Unidad de Control si el OpCode que se
intenta ejecutar es válido. Para el caso, si la señal tiene una valor lógico de „1‟, la instrucción no
existe, por tanto no se almacena la instrucción en el registro R_INST, ni se aumenta el valor del
registro R15. El valor de la señal ILEGAL_OP_DET_OUT tiene prioridad por encima de la señal
OPCODE_DET_OUT, por tanto el valor de esta última no interesa en este caso.
67
Figura 51. Diagrama de Tiempos para operación: Relectura por OpCode ilegal.
9.3.12. Diagrama 12: Fetch16
El diagrama de tiempos mostrado en la Figura 52 incluye las siguientes operaciones: Búsqueda de
instrucciones para instrucciones de 16 Bits. El valor que tenga la señal OPCODE_DET_OUT cuando
haya un dato válido sobre el bus de datos indica a la Unidad de Control si la instrucción es de 16 o 32
Bits. Para el caso, si la señal tiene un valor lógico de „0‟, la instrucción es de 16 Bits. Su valor el resto
del tiempo no interesa. El valor de la señal ILEGAL_OP_DET_OUT cuando haya un dato válido
sobre el bus de datos indica a la Unidad de Control si el OpCode que se intenta ejecutar es válido. Para
el caso, si la señal tiene un valor lógico de „0‟, la instrucción existe.
68
Figura 52. Diagrama de Tiempos para operación: FETCH16.
9.3.13. Diagrama 13: Fetch32
El diagrama de tiempos mostrado en la Figura 53 incluye las siguientes operaciones: Búsqueda de
instrucciones para instrucciones de 32 Bits. El valor que tenga la señal OPCODE_DET_OUT cuando
haya un dato válido sobre el bus de datos indica a la Unidad de Control si la instrucción es de 16 o 32
Bits. Para el caso, si la señal tiene un valor lógico de „1‟, la instrucción es de 32 Bits. Su valor el resto
del tiempo no interesa. El valor de la señal ILEGAL_OP_DET_OUT cuando haya un dato válido
sobre el bus de datos indica a la Unidad de Control si el OpCode que se intenta ejecutar es válido. Para
el caso, si la señal tiene un valor lógico de „0‟, la instrucción existe.
69
Figura 53. Diagrama de Tiempos para operación: FETCH32.
9.4. Anexo: Máquina de Estados De Unidad de Control
A continuación se muestra el diagrama de la máquina de estados que representa a la Unidad de
Control.
70
Figura 54. Máquina de Estados de la Unidad de Control.
La máquina de control consta de 40 pasos. Estos pasos incluyen las acciones de búsqueda de
instrucciones, decodificación, atención a interrupciones y excepciones y todas las 18 instrucciones del
núcleo. La unidad de control se diseño usando el método de diseño “one-hot” [9]. A continuación se
hace una descripción de los cambios en la máquina de control, para cada uno de los estados, por medio
del lenguaje AHPL. Se asume que todas las señales que no aparecen explícitamente en un paso
determinado tienen un valor lógico de „0‟.
S 0.
_ _ _ _ "11"; _ _ _ _ "10";
_ _ '1'; _ _ "000";
_ '1'; '1'; _ '0 ';
MUX BUS DIR IN C MUX R BANK IN C
MUX DEC C MUX PC C
READ ENA ENABLE W ENA
S1
S 1.
_ _ _ _ "11"; _ _ _ _ "10";
_ _ '1'; _ _ "000";
_ '0 '; '0 '; _ '1';
MUX BUS DIR IN C MUX R BANK IN C
MUX DEC C MUX PC C
READ ENA ENABLE W ENA
71
S2
S 2.
_ _ "001"; _ _ '1'; _ _ _ _ "00";
_ _ _ '0 '; _ _ '0 ';
_ _ _ '0 '; _ _ _ '0 ';
_ '1'; '1'; _ '0 ';
MUX PC C MUX DEC C MUX BUS DIR IN C
MUX SR IN C SR CT C
R INST C H R INST C L
READ ENA ENABLE W ENA
_ [2] _ _ _
_ [2] [1] [0] _ _ _
_ [2] [1] [0]
_ [2] [1] [0]
_ [2] [1] [0]
_ [3] _ _ _
_
S3
S3
S32
S32
S32
S37
S39
SR OUT ILEGAL OP DET OUT
SR OUT IRQ IRQ ILEGAL OP DET OUT
SR OUT IRQ IRQ
SR OUT IRQ IRQ
SR OUT IRQ IRQ
SR OUT ILEGAL OP DET OUT
SR [3] _ _ _OUT ILEGAL OP DET OUT
S 3.
_ _ "001"; _ _ '1'; _ _ _ _ "00";
_ _ _ '0 '; _ _ '1';
_ _ _ '1'; _ _ _ '0 ';
_ '0 '; '0 '; _ '1';
MUX PC C MUX DEC C MUX BUS DIR IN C
MUX SR IN C SR CT C
R INST C H R INST C L
READ ENA ENABLE W ENA
_ _
_ _ _ _ [31]
_ _ _ _ [31]
S4
S7
S9
OPCODE DET OUT
OPCODE DET OUT R INST OUT
OPCODE DET OUT R INST OUT
S 4.
_ _ "001"; _ _ '1'; _ _ _ _ "00";
_ _ _ '0 '; _ _ '0 ';
_ _ _ '0 '; _ _ _ '0 ';
_ '1'; '1'; _ '0 ';
MUX PC C MUX DEC C MUX BUS DIR IN C
MUX SR IN C SR CT C
R INST C H R INST C L
READ ENA ENABLE W ENA
S5
S 5.
_ _ "001"; _ _ '1'; _ _ _ _ "00";
_ _ _ '0 '; _ _ '0 ';
_ _ _ '0 '; _ _ _ '1';
_ '0 '; '0 '; _ '1';
MUX PC C MUX DEC C MUX BUS DIR IN C
MUX SR IN C SR CT C
R INST C H R INST C L
READ ENA ENABLE W ENA
_ _ [31]
_ _ [31]
S6
S8
R INST OUT
R INST OUT
72
S 6.
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
S10
S11
S12
S13
S14
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
S15
S16
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
S 7.
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
S2
S18
S19
S20
S21
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27] _ [4]
_ _ [29] _ _ [28] _ _ [27] _ [4]
S22
S35
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT SR OUT
R INST OUT R INST OUT R INST OUT SR OUT
S 8.
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
S23
S26
S28
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
S 9.
_ _ [29] _ _ [28] _ _ [27]
_ _ [29] _ _ [28] _ _ [27]
S29
S30
R INST OUT R INST OUT R INST OUT
R INST OUT R INST OUT R INST OUT
S 10.
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "00";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S 11.
73
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "00";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S36
V
V
S 12.
1_ _ '0 '; _ _ '1'; _ 2 _ '1';
_ _ _ "00";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S 13.
1_ _ '0 '; _ _ '1'; _ 2 _ '1';
_ _ _ "00";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S36
V
V
S 14.
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "01";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S 15.
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "10";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S 16
74
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "11";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '1'; _ '0 ';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S17
S 17.
1_ _ '0 '; _ _ '0 '; _ 2 _ '0 ';
_ _ _ "00";
_ _ _ _ "00"; _ _ "000"; _ _ '0 ';
_ _ '0 '; _ '1';
OP SOURCE C SEL CARRY C SEL OP C
ALU SEL OUT C
MUX R BANK IN C MUX PC C MUX DEC C
R ALU C W ENA
S2
S 18.
1_ _ '0 '; _ _ '1'; _ 2 _ '1';
_ _ '0 '; _ _ _ '0 ';
_ _ '1'; _ _ '1';
OP SOURCE C SEL CARRY C SEL OP C
SEL COMPARACION C MUX SR IN C
SR N C SR Z C
S2
S 19.
1_ _ '0 '; _ _ '1'; _ 2 _ '1';
_ _ '1'; _ _ _ '0 ';
_ _ '1'; _ _ '1';
OP SOURCE C SEL CARRY C SEL OP C
SEL COMPARACION C MUX SR IN C
SR N C SR Z C
S2
S 20.
_ _ _ '0'; _ '0'; _ _ '1';MUX SR IN C IE IN SR IE C
S2
S 21.
_ _ _ '0'; _ '1'; _ _ '1';MUX SR IN C IE IN SR IE C
S2
S 22.
_ _ "010"; _ _ '1'; _ _ _ '1';
_ _ '1'; _ _ '1'; _ _ '1'; _ _ '1'; _ _ '1';
_ '1';
MUX PC C MUX DEC C MUX SR IN C
SR N C SR Z C SR IE C SR CT C SR IA C
W ENA
S2
75
S 23.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ _ _ "01"; _ _ _ _ "10"; _ _ '0 ';
_ _ "000";
_ _ '1'; _ '0 '; '0 '; _ '0 ';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX BUS DIR IN C MUX R BANK IN C MUX DEC C
MUX PC C
R ALU C READ ENA ENABLE W ENA
S24
S 24.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ _ _ "01"; _ _ _ _ "10"; _ _ '0 ';
_ _ "000";
_ _ '0 '; _ '1'; '1'; _ '0 ';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX BUS DIR IN C MUX R BANK IN C MUX DEC C
MUX PC C
R ALU C READ ENA ENABLE W ENA
S25
S 25.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ _ _ "01"; _ _ _ _ "10"; _ _ '0 ';
_ _ "000";
_ _ '0 '; _ '0 '; '0 '; _ '1';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX BUS DIR IN C MUX R BANK IN C MUX DEC C
MUX PC C
R ALU C READ ENA ENABLE W ENA
S2
S 26.
1_ _ '0'; _ 2 _ '0'; _ _ '0 ';
_ _ _ "00"; _ _ _ _ "01";
_ _ '1'; _ '0 '; '0 ';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C MUX BUS DIR IN C
R ALU C WRITE ENA ENABLE
S27
S 27.
1_ _ '1'; _ 2 _ '0'; _ _ '0 ';
_ _ _ "00"; _ _ _ _ "01";
_ _ '0 '; _ '1'; '1';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C MUX BUS DIR IN C
R ALU C WRITE ENA ENABLE
S2
S 28.
_ _ _ _ "01"; _ _ '0 '; _ _ "000";
_ '1';
MUX R BANK IN C MUX DEC C MUX PC C
W ENA
76
S2
S 29.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ '1'; _ _ _ _ "00"; _ _ "000";
_ _ '1'; _ '0 ';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX DEC C MUX R BANK IN C MUX PC C
R ALU C W ENA
_ [0]
_ [0]
S31
S2
SR OUT
SR OUT
S 30.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ '1'; _ _ _ _ "00"; _ _ "000";
_ _ '1'; _ '0 ';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX DEC C MUX R BANK IN C MUX PC C
R ALU C W ENA
_ [1]
_ [1]
S31
S2
SR OUT
SR OUT
S 31.
1_ _ '0 '; _ 2 _ '0 '; _ _ '0 ';
_ _ _ "00";
_ _ '1'; _ _ _ _ "00"; _ _ "000";
_ _ '0 '; _ '1';
OP SOURCE C SEL OP C SEL CARRY C
ALU SEL OUT C
MUX DEC C MUX R BANK IN C MUX PC C
R ALU C W ENA
S2
S 32.
_ _ "000"; _ _ '1';
_ _ _ _ "10"; _ _ _ _ "10";
_ _ _ '0 '; _ '1';
_ _ '1'; _ _ '1'; _ _ '1'; _ _ '1';
_ '0 '; '0 '; _ '0 '; _ '0 ';
MUX PC C MUX DEC C
MUX BUS DIR IN C MUX R BANK IN C
MUX SR IN C IA IN
PC C C SR C C R IRQ C SR IA C
READ ENABLE ENABLE W ENA SR RST
S33
S 33.
_ _ "000"; _ _ '1';
_ _ _ _ "10"; _ _ _ _ "10";
_ _ _ '0 '; _ '1';
_ _ '0 '; _ _ '0 '; _ _ '0 '; _ _ '0 ';
_ '1'; '1'; _ '0 '; _ '1';
MUX PC C MUX DEC C
MUX BUS DIR IN C MUX R BANK IN C
MUX SR IN C IA IN
PC C C SR C C R IRQ C SR IA C
READ ENABLE ENABLE W ENA SR RST
77
S34
S 34.
_ _ "000"; _ _ '1';
_ _ _ _ "10"; _ _ _ _ "10";
_ _ _ '0 '; _ '0 ';
_ _ '0 '; _ _ '0 '; _ _ '0 '; _ _ '0 ';
_ '0 '; '0 '; _ '1'; _ '0 ';
MUX PC C MUX DEC C
MUX BUS DIR IN C MUX R BANK IN C
MUX SR IN C IA IN
PC C C SR C C R IRQ C SR IA C
READ ENABLE ENABLE W ENA SR RST
S2
S 35.
_ _ '1'; _ _ "101"; _ _ _ '0'; _ '1';
_ _ '1'; _ _ '1'; _ _ '1';
_ '1'; _ '0 ';
MUX DEC C MUX PC C MUX SR IN C IA IN
PC C C SR C C SR IA C
W ENA SR RST
S38
S 36.
_ _ '1'; _ _ "100"; _ _ _ '0'; _ '1';
_ _ '1'; _ _ '1'; _ _ '1';
_ '1'; _ '0 ';
MUX DEC C MUX PC C MUX SR IN C IA IN
PC C C SR C C SR IA C
W ENA SR RST
S38
S 37.
_ _ '1'; _ _ "011"; _ _ _ '0'; _ '1';
_ _ '1'; _ _ '1'; _ _ '1';
_ '1'; _ '0 ';
MUX DEC C MUX PC C MUX SR IN C IA IN
PC C C SR C C SR IA C
W ENA SR RST
S38
S 38.
_ _ '1'; _ _ "000"; _ _ _ '0'; _ '0 ';
_ _ '0 '; _ _ '0 '; _ _ '0 ';
_ '0 '; _ '1';
MUX DEC C MUX PC C MUX SR IN C IA IN
PC C C SR C C SR IA C
W ENA SR RST
S2
S 39.
_ _ "001"; _ _ '1'; _ _ _ _ "00";
_ _ _ '0 '; _ _ '1'; _ '1';
_ _ _ '0 '; _ _ _ '0 ';
_ '0 '; '0 '; _ '0 ';
MUX PC C MUX DEC C MUX BUS DIR IN C
MUX SR IN C SR CT C CT IN
R INST C H R INST C L
READ ENA ENABLE W ENA
78
S2
9.5. Anexo: Protocolo de Pruebas
9.5.1. Esquema del Protocolo
Para poder determinar las pruebas que es necesario realizar, se debe establecer qué debe cumplir el
sistema para ser considerado como correcto. De esta forma, es posible enrutar las pruebas hacia el
cumplimiento de esas especificaciones.
El sistema tiene varias partes que deben ser revisadas y analizadas correctamente. Inicialmente, es
necesario poder determinar que los sistemas funcionan de acuerdo a su diseño. Para lograr esto, se
usará la herramienta de desarrollo Quartus II.
9.5.1.1. Prueba de Descripción de Hardware e Interconexiones.
Cuando se tenga completo el diseño del sistema, se hace su descripción por medio del lenguaje
VHDL, en la herramienta mencionada. Inicialmente, se describe cada uno de los componentes básicos
del sistema por separado: tales como Flip Flops, y bloques de lógica. Posterior a su descripción, se
realizan simulaciones de cada bloque, para confirmar que funcionan de acuerdo a lo que se espera. Los
parámetros que indican el correcto funcionamiento de cada componente dependen del componente
específico. Por ejemplo, se espera que una compuerta lógica cumpla con su ecuación combinatoria de
forma correcta.
Con estos componentes básicos han sido descritos y su funcionamiento comprobado por medio de
simulación, se empieza a hacer la integración de estos componentes para la formación de los bloques
más básicos del sistema, tales como registros y multiplexores. Estos bloques siguen el mismo proceso
que los componentes básicos con los cuales se construyen: descripción en VHDL basada en los
componentes básicos cuyo funcionamiento ha sido comprobado por simulación, simulación de los
bloques y correcciones necesarias para asegurar su correcto funcionamiento.
El proceso de integración y simulación paso a paso, anteriormente descrito, se sigue realizando hasta
llegar a integrar todo el sistema. Una vez hecho esto, se comprueba su funcionamiento por medio de
simulación. Puesto que la unidad de datos no está conectada a la memoria ni a la unidad de control, es
necesario proveer estas señales a la misma. Esto se hace, paso a paso con cada una de todas las
posibles operaciones que la unidad de datos es capaz de realizar. Se monitorean los buses de datos y
de direcciones para comprobar que la información está siguiendo los caminos correctos a través de la
unidad de datos.
Una vez que esto ha sido comprobado por simulación, se describe la unidad de control, por medio del
lenguaje AHPL. Una vez que todas las ecuaciones lógicas para transición de estados han sido
caracterizadas, se hace la descripción de su comportamiento en VHDL. Para verificar su
funcionamiento por simulación, se introducen las señales que ésta espera en sus entradas, y se verifica
que ésta pase por todos los estados necesarios de acuerdo a esa información. Finalmente, se hace la
integración de ambas unidades. De esta manera, es posible empezar a introducir instrucciones al
sistema, de forma manual y controlada, para verificar luego los resultados de las mismas, y comprobar
que el sistema funciona correctamente.
En caso de que una instrucción no realice las operaciones que se esperan, se verifica la descripción de
todo el sistema. Es importante identificar y verificar interconexiones entre bloques y entre las dos
unidades, pues es más probable que se cometan errores en estas secciones de la descripción que en los
bloques como tal, dado que los bloques, en este momento de la integración, han sido comprobados de
manera independiente en su funcionamiento. Si la descripción es correcta, se requiere verificar el
diseño de la o las instrucciones y operaciones que están generando inconvenientes, y de ser necesario,
se rediseñan para que cumplan con las especificaciones.
79
Cuando todo el sistema integrado ha sido comprobado, se hace una última simulación, conectando el
sistema a la memoria interna del chip FPGA. Sobre esta memoria se escribe un programa que haga uso
de la mayor cantidad de recursos del sistema.
La compilación de todo el sistema indicará la máxima velocidad a la que puede trabajar el reloj, de
forma que los tiempos de hold y setup no afecten la funcionalidad de las operaciones del sistema. Se
espera que el sistema pueda funcionara con la velocidad objetivo mínima de 50MHz. En caso de que
el sistema no logre funcionar a esta velocidad de reloj, se utiliza la herramienta de análisis de ruta
crítica del software Quartus II, para determinar donde se encuentra el lugar o lugares físicos del chip
FPGA que impiden su operación a esta velocidad. Después de esto, se procede a la optimización de
dichas rutas, para determinar si es posible lograr la velocidad de reloj deseada.
Cuando todas las pruebas de simulación hayan sido ejecutadas de forma satisfactoria de acuerdo al
diseño del sistema, es necesario comprobar su funcionamiento real. Para esto, se programa el chip
FPGA con el sistema simulado en correcto funcionamiento, programado a través de una tarjeta de
desarrollo y el software Quartus II. Para poder determinar que el sistema real está funcionando
correctamente, se debe hacer uso de un analizador de estados lógicos que permita revisar los buses del
sistema, así como el estado de los registros internos. Para lograr esto, se requiere enrutar algunas
señales hacia terminales externos del FPGA, a donde se pueda conectar el analizador. El programa que
se usa para la comprobación sobre hardware es el mismo usado en simulación. Esto se hace con el fin
de poder comprobar que el hardware y la simulación entregan resultados iguales. Dada la cantidad de
depuración que el sistema ha tenido hasta este momento, se espera que funcione adecuadamente el
hardware de igual forma a como lo hace en simulación. En caso de no ser así, es necesario verificar
dónde existen diferencias y hacer la corrección de errores que sea necesaria.
9.5.1.2. Prueba del Sistema por medio de un Programa.
El programa que se encargará de demostrar el funcionamiento del sistema está basado en el desarrollo
de la Serie de Fibonacci. Esta serie indica que:
F0 = 0. F1 = 1.
Fn = Fn-2 + Fn-1, n>1, n natural.
Se empezará el desarrollo de la serie, hasta el momento en que la unidad aritmética del sistema genere
una excepción por overflow aritmético. En la rutina de excepción, se reinicia la serie desde cero.
Además del cálculo de la serie, hay un registro que se encarga de aumentar en 1 su valor, una vez que
se genera un cálculo nuevo.
Se implementará un contador interno en el sistema. Este contador se conectará a una de las líneas de
interrupción del núcleo. El contador contará con una señal de reinicio, conectada al bus de direcciones
del sistema. Este esquema permite interrumpir al sistema cada cierto tiempo. En la rutina de
interrupción se reinicia el valor del contador hasta cero, y se modifica otra bandera. De esta manera,
cuando la rutina principal revise esta segunda bandera y vea que está activa, copiará el registro que
guarda el valor que guarda el número de cálculo realizado en memoria. Luego de la copia, borra la
bandera.
El lugar en el cual se almacenan los conteos de la serie es en una lista circular. Para la lista se tiene un
valor de dirección que corresponde a la cabeza y otro que corresponde a la cola, además de un
apuntador que está siempre apuntando al lugar de la lista donde se escribirá el siguiente valor. Cuando
dicho apuntador llegue a ser igual a la dirección de la cola de la lista, será reiniciado con el valor de la
cabeza.
Además de todo lo anterior, siempre que se reinicie el cálculo de la serie desde cero, se leerán desde
memoria los últimos dos datos almacenados en la lista que almacena los conteos. Estos dos datos serán
comparados para saber cual es el mayor de ambos y restar de este el menor. El resultado de esta resta
será la cantidad de elementos de la serie que han sido calculados en el tiempo que ocurren dos
80
interrupciones consecutivas. Sabiendo esto, y además cuántas instrucciones se ejecutan en el cálculo
de un solo elemento de la serie, es posible obtener un dato estimado de cuantas instrucciones ejecuta el
núcleo por segundo (sabiendo el tiempo que transcurre entre una interrupción y la siguiente, que es
igual a un tiempo específico indicado por el contador conectado a la línea de interrupciones).
A continuación se muestran los diagramas de flujo del programa que se implementará para probar el
funcionamiento del núcleo.
81
Figura 55. Diagrama de Flujo de la rutina principal del programa de prueba.
Figura 56. Diagrama de Flujo de la sub-rutina de almacenamiento de datos en las listas en memoria.
82
Figura 57. Diagrama de Flujo de la sub-rutina de carga de datos desde la lista 2, que almacena el subíndice de la serie.
Figura 58. Diagrama de Flujo de la rutina de atención a Interrupción por el contador externo.
83
Figura 59. Diagrama de Flujo de atención a Excepción por overflow aritmético.
9.5.2. Codigo Fuente de Programa de Prueba
El código en lenguaje pseudoensamblador para el programa de prueba se encuentra implementado en
el siguiente archivo de texto, en el disco adjunto:
o root:\Protocolo de Pruebas\Programa de Prueba\Protocolo.txt
Para su visualización, se recomienda abrirlo con el editor de texto de Windows, con la ventana
maximizada, y quitando la opción “Ajuste de Línea” del menú “Formato”.
9.6. Anexo: Simulaciones
Para poder determinar el correcto funcionamiento del diseño, es necesario realizar pruebas sobre este.
Las pruebas no solo se limitan a su realización en el momento de su implementación en hardware. Es
necesario poder validar que el diseño es correcto antes de su implementación. Para esto, es necesario
realizar simulaciones del diseño. Esto permite la corrección de errores del sistema, así como su
depuración, reduciendo el tiempo gastado en estas tareas sobre el hardware en sí.
9.6.1. Núcleo
9.6.1.1. Simulación 1.
La primera simulación realizada se puede reproducir abriendo el proyecto que representa al núcleo,
por medio de los siguientes pasos:
Abrir el proyecto en el archivo que se encuentra en el disco adjunto:
o root:\VHDL\NUCLEO\NUCLEO.qpf
Compilar el proyecto.
En el menú Assignments Settings… Simulator Settings, escoger como archivo de
simulación NUCLEO_Sim1.vwf.
Correr la simulación.
Los pasos que se realizan en esta simulación son los siguientes:
1. Se cargan los datos 0x0001 a 0x000E en los registros R1 a R14 respectivamente, usando LDI.
84
2. Se almacenan en memoria los datos que hay en los registros R0 a R15, en la dirección R0+R0,
R1+R1, y así sucesivamente hasta R15+R15. Se usa STR.
3. Se realiza la suma UADD R1 = R2 + R10.
4. Se realiza la suma SADD R2 = R3 + R11.
5. Se realiza la resta USUB R3 = R4 – R12.
6. Se realiza la resta SSUB R4 = R5 – R13.
7. Se realiza la operación lógica AND R5 = R6 and R14.
8. Se realiza la operación lógica OR R6 = R7 or R15.
9. Se realiza la operación lógica NOR R7 = R8 nor R0.
10. Se carga desde memoria, en la dirección R11+R3, un dato, y se guarda en el registro R8.
11. Se ejecuta un NOP.
Esto finaliza la simulación del archivo NUCLEO-Sim1.vwf.
9.6.1.2. Simulación 2.
La segunda simulación realizada se puede reproducir abriendo el proyecto que representa al núcleo,
por medio de los siguientes pasos:
Abrir el proyecto en el archivo que se encuentra en el disco adjunto:
o root:\VHDL\NUCLEO\NUCLEO.qpf
Compilar el proyecto.
En el menú Assignments Settings… Simulator Settings, escoger como archivo de
simulación NUCLEO_Sim2.vwf.
Correr la simulación.
Los pasos que se realizan en esta simulación son los siguientes:
1. Se compara, sin signo, a R1 con R0, y se intenta un salto condicional tipo JLT. El salto no ocurre,
como se espera, pues R1 es mayor a R0, con datos sin signo.
2. Se compara, sin signo, a R0 con R1, y se intenta un salto condicional tipo JLT. El salto ocurre,
como se espera, pues R0 es menor que R1, con datos sin signo.
3. Se compara, con signo, a R0 con R1, y se intenta un salto condicional tipo JLT. El salto no ocurre,
como se espera, pues R0 es mayor a R1, cuando los datos se toman como con signo en
complemento a 2 (0x8000 es igual a -32768, que es menor que 0).
4. Se compara, con signo, a R1 con R0, y se intenta un salto condicional tipo JLT. El salto ocurre,
como se espera, pues R1 es menor que R0, con datos con signo en complemento a 2.
5. Se compara, sin signo, a R1 con R2, y se intenta un salto condicional tipo JEQ. El salto ocurre,
como se espera, pues R1 y R2 son iguales.
6. Se compara, sin signo, a R1 con R0, y se intenta un salto condicional tipo JEQ. El salto no ocurre,
como se espera, pues R1 y R0 no son iguales.
85
7. Se compara, con signo, a R1 con R2, y se intenta un salto condicional tipo JEQ. El salto ocurre,
como se espera, pues R1 y R2 son iguales.
8. Se compara, con signo, a R1 con R0, y se3 intenta un salto condicional tipo JEQ. El salto no
ocurre, como se espera, pues R1 y R0 no son iguales.
Esto finaliza la simulación del archivo NUCLEO-Sim2.vwf.
9.6.1.3. Simulación 3.
La tercera simulación realizada se puede reproducir abriendo el proyecto que representa al núcleo, por
medio de los siguientes pasos:
Abrir el proyecto en el archivo que se encuentra en el disco adjunto:
o root:\VHDL\NUCLEO\NUCLEO.qpf
Compilar el proyecto.
En el menú Assignments Settings… Simulator Settings, escoger como archivo de
simulación NUCLEO_Sim3.vwf.
Correr la simulación.
Los pasos que se realizan en esta simulación son los siguientes:
1. Se ejecuta la instrucción de habilitación de interrupciones IENA.
2. Se solicita interrupción del sistema por la primera interrupción. El sistema responde cargando el
vector de interrupción que, por diseño corresponde a dicha interrupción.(REFERENCIA A
CONJUNTO DE INSTRUCCIONES)
3. Se termina la rutina de interrupción con la instrucción RTI. Se comprueba que el contador de
programa continúa en la instrucción previa a la atención a interrupción.
4. Se solicita interrupción del sistema por la segunda interrupción. El sistema responde cargando el
vector de interrupción que, por diseño corresponde a dicha interrupción.
5. Se termina la rutina de interrupción con la instrucción RTI. Se comprueba que el contador de
programa continúa en la instrucción previa a la atención a interrupción.
6. Se solicita interrupción del sistema por la tercera interrupción. El sistema responde cargando el
vector de interrupción que, por diseño corresponde a dicha interrupción.
7. Se termina la rutina de interrupción con la instrucción RTI. Se comprueba que el contador de
programa continúa en la instrucción previa a la atención a interrupción.
8. Se ejecuta la instrucción de inhabilitación de interrupciones IDIS.
9. Se solicita interrupción del sistema por la primera interrupción. El sistema responde ignorando la
interrupción y continúa con la ejecución normal del programa.
10. Se solicita interrupción del sistema por la segunda interrupción. El sistema responde ignorando la
interrupción y continúa con la ejecución normal del programa.
11. Se solicita interrupción del sistema por la tercera interrupción. El sistema responde ignorando la
interrupción y continúa con la ejecución normal del programa.
Esto finaliza la simulación del archivo NUCLEO-Sim3.vwf.
86
9.6.1.4. Simulación 4.
La cuarta simulación realizada se puede reproducir abriendo el proyecto que representa al núcleo, por
medio de los siguientes pasos:
Abrir el proyecto en el archivo que se encuentra en le archivo adjunto:
o root:\VHDL\NUCLEO\NUCLEO.qpf
Compilar el proyecto.
En el menú Assignments Settings… Simulator Settings, escoger como archivo de
simulación NUCLEO_Sim4.vwf.
Correr la simulación.
Los pasos que se realizan en esta simulación son los siguientes:
1. Se carga el dato 0x7FFF al registro R1, usando LDI.
2. Se carga el dato 0x8001 al registro R2, usando LDI.
3. Se realiza la suma SADD R1 = R1+R1. Esto genera overflow aritmético, y el núcleo desvía su
ejecución a la dirección de inicio de la rutina de atención a una excepción por overflow aritmético.
4. Se termina la rutina de atención a excepción, usando la instrucción RTI.
5. Se realiza la resta SSUB R2 = R2-R1. Esto genera overflow aritmético, y el núcleo desvía su
ejecución a la dirección de inicio de la rutina de atención a una excepción por overflow aritmético.
6. Se termina la rutina de atención a excepción, usando la instrucción RTI.
7. Se ejecuta una vez mas RTI para comprobar la excepción por uso ilegal de la instrucción (tratar de
salir de una interrupción o excepción cuando no se está dentro de una). El núcleo desvía la
ejecución del programa hacia la rutina de atención a la excepción por uso ilegal de la instrucción
RTI.
8. Se termina la rutina de atención a excepción, usando la instrucción RTI.
9. Se introduce una instrucción no definida, 0x3800, usando el código de operación no definido
0x38.
10. El núcleo reconoce el código de operación no definido y reintenta la lectura de esa posición de
memoria. El código indefinido es reintroducido al núcleo.
11. El núcleo reconoce el código de operación no definido, y teniendo en cuenta que ya intentó la
relectura, desvía la ejecución del programa hacia la rutina de atención a la excepción por código
de operación no válido.
12. Se termina la rutina de atención a excepción, usando la instrucción RTI.
Esto finaliza la simulación del archivo NUCLEO-Sim4.vwf.
9.6.2. Sistema
La simulación del sistema con memorias y un periférico externo se puede reproducir abriendo el
proyecto que representa al sistema bajo esta condición de conexión, por medio de los siguientes pasos:
Abrir el proyecto en el archivo:
87
o root:\VHDL\SISTEMA\SISTEMA.qpf
Compilar el proyecto.
En el menú Assignments Settings… Simulator Settings, escoger como archivo de
simulación Sistema_SIM.vwf.
Correr la simulación.
A continuación se realiza una explicación, en términos de operaciones, de la simulación, usando como
referencia los tiempos en que ocurren las operaciones. Lo más destacado se explicará. Es posible
determinar exactamente qué instrucción se está ejecutando, revisando la señal del bus BUS_DIR, la
cual tiene, la mayoría del tiempo (cuando no se está leyendo o escribiendo sobre la memoria RAM o el
contador síncrono) la dirección almacenada en el Program Counter. Es necesario leer el protocolo de
pruebas, presente en el Anexo ANEXO-PROTOCOLO DE PRUEBAS para entender la simulación.
La simulación se ejecuta con una frecuencia de reloj de 50MHz, con el fin de poder compararla con
los resultados del sistema en tiempo real, sobre la FPGA.
0.1us – 0.5us. Secuencia de inicio del sistema. Se carga el vector de inicio del programa.
0.5us – 9.7us. Inicialización de registros, habilitación de interrupciones.
9,7us – 15.1us. Revisión de interrupción por contador externo.
15.1us – 16.3us. No hubo interrupción. Salto a cálculo del siguiente valor de la serie.
16.3us – 20.5us. Se calcula el siguiente valor de la serie.
20.5us – 21.7us. Salto a aumento de la cantidad de cálculos realizados.
21.7us – 24.3us. Aumenta de la cantidad de cálculos realizados.
24.3us – 27.1us. Revisión del último valor calculado de la serie.
27.1us – 28.3us. Reiniciación de la rutina principal.
28.3us – 102.4us. Repetición de la rutina principal hasta ocurrencia de interrupción por contador
externo.
102.9us – 103.5us. Carga de vector de interrupción para interrupción 1.
103.5us – 109.5us. Ejecución de rutina de atención por interrupción 1, y retorno desde
interrupción.
109.5us – 115.1us. Ejecución de rutina principal. Revisión de ocurrencia de interrupción. Salto a
rutina de almacenamiento de datos en RAM.
115.1us – 128.1us. Ejecución de rutina de almacenamiento de datos en RAM. Retorno a rutina
principal.
128.1us – 211.0us. Repetición de la rutina principal hasta ocurrencia de interrupción por contador
externo.
211.5us – 218.1us. Ejecución de rutina de atención a interrupción 1 y retorno.
218.1us – 501.9us. Ejecución del ciclo principal, con interrupciones y rutinas secundarias, hasta
que ocurre excepción por overflow aritmético.
88
501.9us – 502.3us. Carga del vector de excepción para overflow aritmético.
502.3us – 505.5us. Ejecución de rutina de atención a excepción por overflow aritmético, y retorno
a rutina principal.
515.1us. Salto a rutina de carga de datos desde memoria RAM.
515.1us – 537.2us. Ejecución de rutina de carga de datos desde memoria RAM, hasta ocurrencia
de interrupción por contador externo.
537.2us – 544.3us. Ejecución de rutina de atención a interrupción 1 y retorno.
544.3us – 546.9us. Ejecución del resto de la rutina de carga de datos desde memoria RAM y
retorno a rutina principal.
546.9us – 646.5us. Ejecución de rutina principal hasta ocurrencia de interrupción por contador
externo.
646.5us – 653.3us. Ejecución de rutina de atención a interrupción 1 y retorno.
653.3us – 716.7us, fin de simulación. Ejecución del programa repite el patrón mostrado hasta el
momento.
Esto finaliza la simulación del archivo Sistema_SIM.vwf.
9.7. Anexo: Prueba en FPGA del Sistema
El resultado de la ejecución del programa de prueba sobre el sistema con memorias y un periférico
externo, sobre la FPGA en tiempo real, se puede observar abriendo el archivo de simulación que se
indica a continuación, por medio de los siguientes pasos:
Abrir el archivo de simulación que se encuentra en el disco adjunto:
o root:\VHDL\SISTEMA\Sistema_HW.vwf
El resultado de simulación ha sido consignado en un archivo de simulación de Quartus II, para una
visualización sencilla del mismo.
Como se puede ver, la captura de señales por parte del analizador lógico solo ocurre durante el borde
de subida del reloj CLK, por lo cual algunas señales aparecen corridas medio ciclo de reloj hacia el
futuro, comparadas con las señales que se observan en la simulación del sistema (ver Anexo ANEXO-
SIMULACION-SISTEMA).
A continuación se realiza una explicación, en términos de operaciones, de la simulación, usando como
referencia los tiempos en que ocurren las operaciones. Lo más destacado se explicará. Además de esto,
es posible observar los resultados de los cálculos realizados, en los registros internos.
0.1us – 0.5us. Secuencia de inicio del sistema. Se carga el vector de inicio del programa.
0.5us – 9.7us. Inicialización de registros, habilitación de interrupciones. Se observa que R1 =
0x0000, R2 = 0x0001, R4 = 0x0000, R7 = 0x0200, R8 = 0x02FF, R10 = 0x0200, R13 = 0x0000.
9,7us – 15.1us. Revisión de interrupción por contador externo. Se usa R5 = 0x005C como vector
de salto condicional.
15.1us – 16.3us. No hubo interrupción. Salto a cálculo del siguiente valor de la serie.
89
16.3us – 20.5us. Se calcula el siguiente valor de la serie. Se observa que R3 = 0x0001, R2 =
0x0001, R1 = 0x0001.
20.5us – 21.7us. Salto a aumento de la cantidad de cálculos realizados.
21.7us – 24.3us. Aumenta de la cantidad de cálculos realizados. Se observa que R4 = 0x0001.
24.3us – 27.1us. Revisión del último valor calculado de la serie. Se usa R5 = 0x0072 como vector
de salto condicional.
27.1us – 28.3us. Reiniciación de la rutina principal. Un ciclo de reloj después, se tiene que PC =
0x0034.
28.3us – 63.4us. Repetición de la rutina principal hasta ocurrencia de interrupción por contador
externo. Se observa que el cálculo de la serie continuó: R3 aumentó, ahora R3 = 0x0003, R2 =
0x0003, R1 = 0x0002. Se observa que R4 aumentó, R4 = 0x0003.
63.8us – 64.4us. Carga de vector de interrupción para interrupción 1.
64.4us – 70.4us. Ejecución de rutina de atención por interrupción 1, y retorno desde interrupción.
70.4us – 78.0us. Ejecución de rutina principal. Revisión de ocurrencia de interrupción. Salto a
rutina de almacenamiento de datos en RAM.
78.0us – 91.0us. Ejecución de rutina de almacenamiento de datos en RAM. Retorno a rutina
principal. Se observa que R10 aumenta, R10 = 0x0001.
91.0us – 172.0us. Repetición de la rutina principal hasta ocurrencia de interrupción por contador
externo. Se observa que el cálculo del a serie ha continuado. Ahora se tiene que R3 = 0x0022, R2
= 0x0022, R1 = 0x0015, R4 = 0x0007. Se observa que entre una interrupción y la siguiente
alcanzan a ocurrir entre 4 y 5 cálculos de la serie.
172.6us – 178.8us. Ejecución de rutina de atención a interrupción 1 y retorno.
178.8us – 509.0us. Ejecución del ciclo principal, con interrupciones y rutinas secundarias, hasta
que ocurre excepción por overflow aritmético. Se observa que los últimos dos valores calculados
de la serie son R3 = 0x6FF1 y R2 = 452F, cuya suma genera el valor 6FF1+452F=B520. Este
valor genera overflow aritmético, pues el máximo valor positivo que se puede obtener es 7FFF.
También se observa que se han calculado un total de R4 = 0x0016 valores de la serie.
509.0us – 509.4us. Carga del vector de excepción para overflow aritmético en el Program
Counter.
509.4us – 512.4us. Ejecución de rutina de atención a excepción por overflow aritmético, y retorno
a rutina principal. Se observa la reiniciación de la serie, con R3 = 0x0001, R2 = 0x0000.
522.0us. Salto a rutina de carga de datos desde memoria RAM. Se observa el uso de R5 = 0x0072
como vector de salto condicional.
522.0us – 547.0us. Ejecución de rutina de carga de datos desde memoria RAM, hasta ocurrencia
de interrupción por contador externo. Se observa la carga de datos desde memoria: la dirección
R10 = 0x0203 entrega el dato R5 = 0x0012; la dirección R10 = 0x0202 entrega el dato R6 =
0x000D. Su resta genera R12 = 0x0005 cálculos por cada interrupción. Esto indica que, en efecto,
como se indicó anteriormente, ocurren aproximadamente 5 cálculos de la serie en el tiempo que
toma en entrar una interrupción al sistema.
90
547.0us – 607.2us. Ejecución de rutina principal hasta ocurrencia de interrupción por contador
externo. Se observa que la serie aumenta otra vez su valor: R3 = 0x0003, R2 = 0x0002, R1 =
0x0002. Se han realizado hasta el momento de ejecución del programa un total de R4 = 0x0019
cálculos aritméticos para la serie.
607.2us – 613.8us. Ejecución de rutina de atención a interrupción 1 y retorno.
613.8us – 716.7us, fin de simulación. Ejecución del programa repite el patrón mostrado hasta el
momento. Se observa que el cálculo de la serie va en R3 = 0x0022, y sea han realizado un total de
R4 = 0x001E cálculos. Además, el apuntador a la lista en memoria RAM está en la posición
0x0206, lo que indica que se han atendido un total de 5 interrupciones durante la ejecución.
Esto finaliza el archivo Sistema_HW.vwf.
9.8. Anexo: Código VHDL del Sistema, Distribución del
Directorio de VHDL en el Disco Adjunto
Todo el código VHDL del sistema se encuentra incluido en la siguiente carpeta del disco adjunto:
o root:\VHDL
A continuación se muestra un diagrama de tallos y hojas que representa la estructura de directorios de
la carpeta mencionada, junto con una descripción del proyecto que se encuentra en cada una.
Cada una de las carpetas descritas contiene un proyecto. Para abrir cada proyecto, debe buscarse
dentro de la carpeta, un archivo con el mismo nombre de la carpeta, y terminado en .qpf. Por ejemplo,
para abrir el proyecto CLAU_4, se debe buscar el archivo
root:\VHDL\RECURSOS\CLAU_4\CLAU_4.qpf.
VHDL: Todo el Sistema
o CONTADOR: Contador síncrono de 10 Bits.
o MUX_BUS_READ: Multiplexor a la entrada del bus de lectura del núcleo, para pasar
datos de la ROM o la RAM, dependiendo de qué dirección se lea.
o NUCLEO: El núcleo, implementado sin memorias ni periféricos.
o RAM: Unidad de memoria RAM de 4096 posiciones.
o RECURSOS: Bloques básicos usados repetidamente a lo largo de todo el diseño.
CLAU_4: Unidad de carry look-ahead de 4 bits.
FA_GP_1: Full adder de 1 bit, con señales Generate y Propagate.
FA_LAC_4: Full adder con unidad de carry look-ahead, de 4 bits.
FF_D_N: Flip Flop con Enable que reacciona a borde de bajada del reloj.
FF_D_P: Flip Flop con Enable que reacciona a borde de subida del reloj.
HA_1: Half adder de 1 bit.
MUX_2_1: Multiplexor 2:1 de 1 bit.
91
MUX_4_1: Multiplexor 4:1 de 1 bit.
MUX_8_1: Multiplexor 8:1 de 1 bit.
MUX_16_1: Multiplexor 16:1 de 1 bit.
REGISTRO16: Registro de 16 bits.
o ROM: Unidad de memoria ROM de 512 posiciones.
ROM_PROT.mif: Archivo de inicialización de la memoria ROM, directamente
editable con la herramienta Quartus II, e incluido en el proyecto. Se abrirá una
ventana con filas y columnas, representando las posiciones de memoria.
o SISTEMA: Sistema de prueba, con núcleo, RAM, ROM y contador síncrono.
Sistema_HW.vwf: Archivo de forma de ondas digitales que muestra el resultado
de la prueba del sistema sobre el chip FPGA. Archivo capturado por medio del
analizador lógico embebido sintetizado por la herramienta de Altera SignalTap II,
incluida en el entorno de desarrollo Quartus II, a través de la conexión JTAG
entre el chip FPGA y un puerto USB del computador, usando el periférico USB
Blaster incluido en el kit de desarrollo con el cual se trabajó.
o U_CONTROL: Máquina de control del núcleo.
o U_DATOS: Unidad de datos del núcleo.
ALU: Revisar secciones 4.1.3.1, 9.1.
U_ARIT: Revisar sección 9.1.1.
× NEGADOR: Revisar sección 9.1.1.1.
× SEL_CARRY: Revisar sección 9.1.1.3.
× SEL_OP2: Revisar sección 9.1.1.2.
× SUMADOR: Revisar sección 9.1.1.4.
U_AND: Revisar sección 9.1.2.
U_OR: Revisar sección 9.1.2.
LOGICA_COMPARACION_CERO: Revisar sección 9.1.5.
LOGICA_OVERFLOW: Revisar sección 9.1.3.
U_NOR: Revisar sección 9.1.2.
ALU_SEL_OUT: Revisar sección 9.1.6.
SEL_COMPARACION: Revisar sección 9.1.4.
R_BANK: Revisar secciones 4.1.4.1, 9.2.
BANCO: Revisar Secciones 9.2.1, 9.2.2.
× BANK_DEC: Revisar sección 9.2.2.1
92
× MUX_DEC: Revisar sección 9.2.2.2.
× MUX_PC: Revisar sección 9.2.1.3.
× PC_ADD: Revisar sección 9.2.1.2.
MUX1: Revisar sección 9.2.3.1.
MUX2: Revisar sección 9.2.3.1.
MUX_OP1: Revisar sección 9.2.3.2.
MUX_R_BANK_IN: Revisar sección 4.1.3.5.
MUX_SR_IN: Revisar sección 4.1.3.4.
MUX_BUS_DIR_IN: Revisar sección 4.1.3.6.
R_ALU: Revisar sección 4.1.4.5.
R_INST: Revisar sección 4.1.4.2.
R_IRQ: Revisar sección 4.1.4.6.
SR: Revisar sección 4.1.4.3.
SR_C: Revisar sección 4.1.4.4.
OPCODE_DET: Revisar sección 4.1.3.2.
ILEGAL_OP_DET: Revisar sección 4.1.3.3.