ARQUITECTURA DE COMPUTADORAS Docentes: Prof. Oscar Montes Prof. Obadiah Oghoerore Alegbe Prof. Roberto García Tema: Apuntes de la asignatura Año: 2011 Procesos En el modelo de procesos todo software ejecutable de la computadora, inclusive el sistema operativo, se organiza en varios procesos secuenciales que de aquí en adelante llamaremos PROCESOS. Un proceso es en general parte de un programa con determinadas características: incluye el estado propio del proceso, además del estado del registro de la CPU y tiene la intención de ser ejecutado por el SO (está cargado en RAM). Las antiguas CPUs sólo podían ejecutar un proceso a la vez, pero como lo ejecutaban muy rápidamente, el operador podía tener la sensación de que había muchos procesos ejecutándose a la vez. En este momento existen CPUs capaces de ejecutar dos, tres y hasta cuatro procesos juntos, dependiendo de su estructura interna. La diferencia entre un proceso y un programa es sutil pero crucial. Podríamos utilizar la siguiente analogía para aclarar este punto. Consideremos un científico de la computación con una mente culinaria, que está cocinando el pastel de cumpleaños de su hija. Él tiene una receta para un pastel de cumpleaños y una cocina bien abastecida con los ingredientes necesarios. En esta analogía la receta es el programa (un algoritmo expresado en cierta notación adecuada), el científico es el procesador (CPU) y los ingredientes necesarios para el pastel son los datos de entrada. El proceso es la actividad en la que el cocinero lee la receta, busca los ingredientes, y cocina el pastel (listo, bloqueado, en ejecución). Imaginemos ahora que un hijo del científico entra corriendo, llorando y diciendo que lo picó una abeja. El científico registra el punto de la receta donde se quedó (el estado del proceso activo se resguarda, se memoriza), busca un libro de primeros auxilios, comienza a leer las instrucciones de éste y busca los elementos en el botiquín. El científico atiende la picadura de su hijo y luego de que éste se calma, regresa al punto de elaboración del pastel. El proceso entonces se puede entender como una actividad especial de cierto tipo, en donde hay un programa, entradas y salidas de datos y tiene un estado determinado. Estados de un proceso Aunque cada proceso se debe considerar como una entidad independiente (contador de programa, estado interno, uso de memoria, etc.) es frecuente que los procesos deban interactuar unos con otros. Un proceso podría generar cierta salida que fuera la entrada necesaria para otro proceso. Desde este modelo, se utilizan tres estados fundamentales: a) Listo: el programa (o parte de él) se carga en la memoria de trabajo (RAM). La característica de estar cargado en RAM, es decir, la intención cierta del SO de ejecutar esas instrucciones, convierten al programa en un proceso. b) Ejecución: en este estado la CPU tiene el control de las operaciones de ese proceso y las va cumpliendo en un orden determinado. Este estado es propio del núcleo de la CPU, y cada uno de ellos (si es que tuviera varios) puede tener a un proceso en este estado en un tiempo muy breve que tomaremos como menor al tiempo de instrucción. 2 c) Bloqueado: cuando el proceso requiere de datos que se deban buscar en periféricos de entrada lentos (cuyo acceso involucre una cantidad razonablemente grande de tiempos de instrucción) el SO pone al proceso en este estado de espera y conmuta a la CPU a otro proceso. Planificación de procesos Cuando hay más de un proceso ejecutable, el SO debe decidir el orden de ejecución. Esta parte del SO se denomina PLANIFICADOR, y la lógica para esas decisiones están implícitas en el algoritmo de planificación. En los tiempos pasados, los sistemas de procesamiento eran por lotes (batch), porque eran grupos o lotes de tarjetas perforadas que se debían procesar desde el comienzo hasta el fin. Cuando se pensó en multiprocesamiento, el planificador ya no era un operador, sino que debía ser algo que tomara decisiones más rápidamente. El planificador debe poder atender a ciertas características para su operación: a) Equidad: garantizar que cada proceso obtiene un tiempo de CPU. b) Eficacia: mantener a la CPU ocupada el 100 % del tiempo. c) Tiempo de respuesta: minimizar el plazo de ejecución entre varios procesos. d) Tiempo de regreso: minimizar el tiempo que deben esperar otros procesos para entrar en ejecución. e) Rendimiento: maximizar el número de tareas procesadas en la unidad de tiempo. Un poco de reflexión muestra que algunas de estas condiciones se excluyen mutuamente. Se puede demostrar (Kleinrock 1975) que cualquier algoritmo de planificación que favorezca algún tipo de tarea afecta a otra. Por ejemplo: para darle más tiempo de CPU a un usuario (proceso), hay que darle menos a otro, ya que el tiempo de CPU es finito. Una primera aproximación a un algoritmo de planificación será la elección entre dos modos de asignación de la CPU: El modo apropiativo y el modo no apropiativo, vistos desde el SO, es decir: si el SO va a poder conmutar la CPU en cualquier instante de la ejecución de un proceso (apropiativa) o el proceso se va a ejecutar de principio a fin sin ceder el uso de la CPU (no apropiativa). Algunos de los tipos más comunes de planificación son: a) FCFS: (First Come First Served) es una cola de espera tradicional en la que los procesos se van ejecutando en el orden de llegada. Esta cola se puede considerar aleatoria (al azar) y por lo tanto no implica planificación alguna. Es en general el peor algoritmo de planificación para cualquier recurso, aunque también el más fácil de implementar. b) Round Robin: este método es apropiativo y básicamente es el otorgamiento de la CPU durante tiempos definidos denominados Quantum. c) Por Prioridades: la hipótesis implícita en la planificación anterior es que todos los procesos tienen igual importancia. Esto no es cierto en la realidad, ya que el proceso de actualización del reloj no puede esperar. Un defecto propio de la ejecución por prioridades es que procesos de muy baja prioridad lleguen a no ejecutarse jamás; este jamás es literal, ya 3 que en una universidad de EEUU se encontró por casualidad un proceso que llevaba meses esperando la ejecución. Esta “deficiencia” de este tipo de planificaciones se soluciona habitualmente incorporando la variable “tiempo de espera” haciendo que la prioridad del proceso se incremente automáticamente con el transcurso del tiempo. d) Colas Múltiples: trata de solucionar algunos problemas de la planificación por prioridades, teniendo en cuenta una característica de los procesos que lleva a considerar el tiempo necesario de ejecución dado por el uso de la CPU en operaciones matemáticas o la preponderancia del trabajo con datos externos. Estas clasificaciones se conocen con el nombre de “limitado por CPU” y “limitado por E/S”. e) SJF (Shortest Job First): esta planificación tiende a mejorar el tiempo de respuesta, poniendo en ejecución primero los trabajos más cortos y luego los más largos; el tiempo de regreso también se acorta, aunque el tiempo total es el mismo. Una desventaja importante de este tipo de planificaciones es que el SO debería conocer con anticipación cuánto tiempo de CPU requiere cada proceso, y el sistema no cuenta con capacidad de adivinación. Interrupciones En la analogía del científico computador pastelero, se hizo referencia a un cambio de procesos. Si usamos una planificación del tipo de prioridades, que se considera apropiativa, el SO dependiendo de los procesos, deberá conmutar a la CPU en el instante en que un proceso de mayor prioridad necesite ejecutarse. En la figura de arriba, el servicio del disco es un proceso que tiene prioridad sobre el servicio de comunicaciones, éste es más prioritario que el servicio de impresora, y éste es más prioritario que el programa en ejecución. 4 En una interrupción se debe guardar el estado de la CPU (los registros más importantes) y luego comenzar la ejecución del proceso siguiente. El Bloque de control del proceso o en inglés PCB (Process Control Block) es un registro especial donde el sistema operativo agrupa toda la información que necesita conocer respecto a un proceso particular. Cada vez que se crea un proceso el sistema operativo crea el PCB correspondiente para que sirva como descripción en tiempo de ejecución durante toda la vida del proceso. Cuando el proceso termina, su PCB es borrado y el registro puede ser utilizado para otros procesos. Un proceso resulta conocido para el sistema operativo y por tanto elegible para competir por los recursos del sistema sólo cuando existe un PCB activo asociado a él. El bloque de control de proceso es una estructura de datos con campos para registrar los diferentes aspectos de la ejecución del proceso y de la utilización de recursos. La información almacenada en un PCB incluye típicamente algunos o todos los campos siguientes: Identificador del proceso (Process Identificator -PID-, de sus siglas en Inglés). Estado del proceso. Por ej. listo, en espera, bloqueado. Contador de Programa: Dirección de la próxima instrucción a ejecutar. Valores de registro de CPU. Se utilizan también en el cambio de contexto. Espacio de direcciones de memoria (registro base, registro límite). (Ver “Paginación”) Prioridad, en caso de utilizarse dicho algoritmo para planificación de CPU. Lista de recursos asignados (incluyendo descriptores de archivos y sockets abiertos). Estadísticas del proceso (tiempos de ejecución históricos, limitación de la CPU). Datos del propietario (owner), habitualmente denominado “padre/hijos”. Permisos asignados. Comunicación entre periféricos Habitualmente se suelen denominar periféricos a elementos agregados fuera del gabinete de la computadora, en nuestro caso nos centraremos en la CPU y usaremos ese vocablo para denominar elementos que se comunican con la CPU. En la arquitectura Harvard, existen líneas de comunicación (buses) diferentes para los datos y el programa, este tipo de arquitectura es el que usa el µC PIC; en el caso habitual de las PCs se usa la arquitectura Von Neumann, cuya característica es que datos y programa están ubicados en la RAM y por lo tanto comparten el bus de datos. En las PCs existen tres buses diferenciados por su utilidad: el ya mencionado de datos, el de direcciones y el de control. Los de datos y direcciones quedan definidos por su nombre, el de control son todas las líneas de comunicación que no puedan ser definidas ni como datos ni como direcciones. 5 En la figura anterior se esquematiza el modelo de Harvard, siendo cada uno de los bloques un periférico y uno de ellos la CPU. El bus de datos puede incluir entre 32 y cientos de líneas, cuyo número se conoce como anchura del bus de datos. Puesto que cada línea solo puede transportar un bit en cada instante, el número de líneas determina cuántos bits se pueden transferir al mismo tiempo. La anchura del bus es un factor clave a la hora de determinar las prestaciones del conjunto. Por ejemplo, si el bus de datos tiene una anchura de 8 bits (1 byte) y las instrucciones son de 16 bits, entonces el procesador debe acceder al módulo de memoria dos veces por cada ciclo de instrucción y si busca un dato numérico tipo doble precisión, deberá acceder 4 veces. Las líneas de dirección se utilizan para designar el emisor y el receptor de la comunicación y/o la ubicación del dato o instrucción buscado (RAM). Claramente, la anchura del bus de direcciones determina la máxima capacidad de memoria direccionable en forma directa del sistema. En algunos casos de esta estructura se posiciona el programa en la parte baja de la memoria y los datos en la parte alta de la memoria, como la manera de hacer sencillo el cambio entre instrucciones y datos, cambiando el bit más significativo (MSB). Las líneas de control se utilizan como procedimientos pre y post comunicacionales (Handshaking). Las señales de control transmiten tanto órdenes como información entre los módulos del sistema. Entre las más comunes están las señales de: Memory Write; Memory Read; I/O Read; I/O Write; diferentes señales tipo ACK (Aknowledge); Bus Request; Bus Grant; Interrupt Req; Interrupt ACK; etc. Temporización de los buses Las comunicaciones se pueden clasificar en Síncronas o Sincrónicas y Asincrónicas. Las primeras están relacionadas directamente a un reloj (clock). Las comunicaciones en los buses son sincrónicas, aunque la frecuencia del reloj que las rige es diferente del reloj que comanda la CPU. En la actualidad se pueden encontrar buses de 400 MHz y 800 MHz, cuando el de la CPU está cerca de los 2000 MHz (2 GHz). La figura siguiente representa en forma simplificada un ciclo de lectura/escritura de la RAM. 6 En el gráfico aparecen los datos del reloj, el bus de direcciones, el bus de control y el de datos. Generalmente se utilizan los flancos de bajada de la señal para efectuar la operación indicada por el nombre de la señal en cuestión. La comunicación comienza en el bus de control con las líneas de estado y después de un breve lapso aparecen la dirección del periférico (en un gran porcentaje de casos, las comunicaciones involucran como emisor o receptor a la CPU). Los periféricos leen esta dirección en el flanco de bajada de la señal “validación de dirección” y tienen un tiempo para compararla con su propia dirección. Esta comparación resulta válida sólo para el periférico que debe intercambiar con la CPU, en este caso, suponemos que es la RAM. Lo que sigue es la activación de la RAM en el modo de lectura (desde el punto de vista de la CPU lectura implica requerir de la RAM un dato específico), a partir de allí la RAM coloca el dato en el bus de datos “línea de datos” y con el flanco descendente de la señal de lectura, la RAM le avisa a la CPU que puede copiar ese dato a sus registros. En la operación de escritura es la CPU la que avisa a la RAM a través de la señal “escritura” que el dato que está en el bus es el que se debe guardar. Las señales de control involucradas en esta comunicación son las de: líneas de estado, validación de la dirección, lectura y escritura. Una vez completado este ciclo que, según la figura, ocuparía tres ciclos del reloj (7,5 nseg), los buses de datos y direcciones se deberán ocupar de otras comunicaciones. Dado que el tiempo es no-humano, un humano supondría que el bus lleva una cantidad de datos muy grande; este proceso se denomina “multiplexión”, en este caso, de tiempo. Debido a la complejidad cada vez mayor del uso del bus de datos, se incorporó al sistema un ÁRBITRO DE BUS. 7 Esta figura representa el método antiguo de arbitraje a través de un terminador de bus, en donde cada periférico o bloque solicitaba el uso del bus a través de un contacto eléctrico. La ubicación física de los bloques determinaba su prioridad, lograda por el tipo de conexión, denominado Daisy Chain. En la actualidad, el árbitro del bus es un periférico más al que acceden cada uno de los bloques solicitando el bus de datos con una línea (control), para identificarse pueden utilizar una línea cada periférico o el bus de direcciones. El árbitro compara esa dirección para otorgarle un nivel de prioridad, y ésta a su vez, indicará cuándo puede usar el bus de datos para la comunicación (ver esquema de Modelo Harvard). Como estudio de caso, daremos algunas características del bus tipo PCI (Peripheral Component Interconnect), patentado inicialmente por la empresa Intel en 1990 para su procesador Pentium, posteriormente la patente fue cedida por Intel al dominio público, lo que motivó a muchos fabricantes de hardware a incorporar esas especificaciones a sus componentes. Como resulta de aplicación electrónica sencilla, y por lo tanto es barato, ha ganado popularidad rápidamente, suplantando a los anteriores (ISA, EISA, Local bus, etc.). El Standard PCI es un bus de 64 bits de datos, con una frecuencia de reloj de 66 MHz y transferencias múltiples de datos (hasta 528 MB/seg o 4,224 Gb/seg.). Esto es decir que el árbitro (también centralizado) puede otorgar el bus a un periférico para que éste transmita un tren de datos seguidos, dependiendo del tráfico de datos en cada instante. Este tipo de transferencia hace a la eficiencia y rapidez de transmisión, características necesarias para adaptarse mejor a las frecuencias de la CPU. 8 A este bus se le pueden acoplar adaptadores para conectar periféricos más lentos o más rápidos que las velocidades normalizadas. El arbitraje es centralizado y sincrónico, utiliza un esquema maestro-esclavo (master-slave) y prioridades para la cesión del bus. Cada dispositivo del bus PCI se conecta al árbitro a través de una línea de petición identificada con un número que por simplificación supondremos relacionado al orden de prioridad de ese dispositivo; el árbitro contesta con una línea también numerada para que el dispositivo reconozca su posibilidad de uso del bus. Ciclos de instrucción Cuando hablamos de los procesos los relacionamos con un programa. Habitualmente estamos acostumbrados a programar en lo que denominamos lenguajes de alto nivel (el nivel se corres- 9 ponde con el grado de abstracción y, por lo tanto, el nivel más alto es el del programador y el nivel más bajo la CPU); como esas instrucciones las debe entender la CPU lo que denominamos compilador es el encargado de traducir las estructuras de alto nivel a niveles compatibles con la CPU. En los párrafos siguientes trataremos de conceptualizar cómo es una instrucción de bajo nivel suponiendo una sentencia de asignación del tipo A=B+A Esta sentencia debe entenderse como: tomar el contenido de la dirección B de memoria e ingresarlo a uno de los registros de la CPU; tomar el contenido de la dirección A, ubicarlo en otro registro de la CPU; sumar los valores de los datos obtenidos y, por último, ubicar el resultado en la dirección A (el dato previo de la dirección A se pierde). El método que vamos a ejemplificar supone un direccionamiento directo de la memoria RAM (más adelante veremos que hay otros modos de direccionamiento de la RAM). Proponemos considerar que las primitivas del procesador usado son 16, lo cual implica códigos de operación para la CPU de cuatro bits. En la actualidad se usa combinar en una instrucción los códigos de operación y las direcciones de memoria. Para este esquema, si queremos direccionar hasta 2.048 Bytes de RAM necesitaremos un formato de instrucción de 16 bits, como indica la siguiente figura. En la figura, los códigos de operación a usar se limitan a cargar la instrucción de bajo nivel al registro acumulador desde la memoria; sumar a ese acumulador un dato desde la memoria y almacenar el resultado del acumulador en una posición de memoria. En la figura siguiente se esquematiza la operación A=B+A simplificando el formato de instrucción a números decimales y suponiendo que A=941 y B=940, siendo éstas las direcciones que contendrán los datos a sumar. AC representa el registro Acumulador (o de trabajo para el PIC) IR representa el registro del decodificador de instrucciones 10 Las posiciones de RAM están identificadas por su dirección y el contenido (en este caso se respeta el orden bajo para el programa y el alto para datos). En el caso particular del µC PIC el formato de instrucciones es de 14 bits; 8 corresponden a las direcciones y 6 a los códigos de operación. Los pasos del esquema anterior involucran las siguientes operaciones: Paso 1: el PC (Program Counter) tiene la dirección 300. En esa dirección existe la instrucción compuesta de 1 (cargar AC desde la memoria) y 940, que es la dirección de memoria. Este dato (1 940) se copia en el registro de instrucción y se decodifica: código de operación y dirección de memoria. Paso 2: se efectúa un ciclo de lectura de la RAM a la posicición 940 y se copia el dato de esa dirección (valor 3) al acumulador. Paso 3: el PC tiene la dirección 301. En esa dirección existe la instrucción compuesta de 5 941, que se copia en el registro de instrucción y se decodifica: código de operación y dirección de memoria (código de operación 5 es sumar al acumulador un dato de memoria) 11 Paso 4: ejecuta la suma entre el acumulador y el dato de la posición de memoria 941. Paso 5: el PC (Program Counter) tiene la dirección 302. En esa dirección existe la instrucción compuesta de 2 (almacenar el acumulador en una posición de memoria) y 941, que es la dirección de memoria. Este dato (2 941) se copia en el registro de instrucción y se decodifica. Paso 6: se ejecuta la instrucción 302, con lo cual se copia el contenido del acumulador a la posición de memoria 941. Modos de direccionamiento Ya sea por las operaciones a realizar o para lograr direccionamientos más amplios, existen varias formas de que en una instrucción se haga referencia al dato de memoria. Algunos de esos modos están graficados a continuación. 12 Direccionamiento Inmediato Es la forma más sencilla de direccionamiento porque en realidad no es un direccionamiento. En la instrucción (codop+operando) el código de operación siempre indica la operación matemática o lógica a realizar. En el resto del código de la instrucción en este modo simplemente está el valor para usar en la operación, es decir, no hay acceso adicional a memoria para tener el valor. Direccionamiento Directo 13 Los bits que en la instrucción anterior contenían el operando ahora contienen una dirección física de la memoria donde reside el dato a utilizar. La limitación de este direccionamiento está en la cantidad de bits destinada a las direcciones. Direccionamiento Indirecto En este caso los bits contienen una dirección de memoria cuyo contenido es la dirección de memoria que contiene el dato. Con este esquema podríamos usar, por ejemplo, 4 bits en el campo de direcciones de la instrucción (16 posibilidades) y a través de una tabla residente en RAM de 16 posiciones de 1 byte, accederíamos en realidad a 256 posiciones por cada una de las entradas de la tabla, en total, 16x256=4096 posiciones. Dependiendo del microprocesador esta tabla puede estar dentro de alguna zona de memoria interna con lo cual se ahorraría uno de los accesos a memoria externa. Direccionamiento con Desplazamiento Dentro de estos los más conocidos son el relativo y el indexado. Relativo: Este direccionamiento requiere que las instrucciones tengan dos campos de direcciones; el valor contenido en uno de los campos es utilizado directamente y el otro campo de direcciones contiene habitualmente una referencia implícita. La dirección efectiva se consigue sumando la dirección explícita al contenido de la dirección implícita. Esto si bien es complicado de entender mediante texto, es fácilmente entendible si pensamos en una tabla de conversiones de doble entrada. La tabla estará ubicada en forma secuencial en memoria a partir de la dirección explícita de la instrucción. El desplazamiento a los otros renglones de la tabla se guarda en una posición de memoria referenciada indirectamente. Variando el valor de esta última posición de memoria, obtenemos el acceso a cada uno de los renglones de la tabla. Indexado: En este caso la primera porción de la dirección es también relativa, es decir, referencia a una posición de la memoria externa. Memoria Cuando nos referimos a “Memoria”, estamos hablando, específicamente, de la RAM. Es uno de los recursos más importantes de una máquina, ya que interviene en una relación muy estrecha con el microprocesador. En forma básica podemos pensar la memoria física como un estante con muchos cajones y dentro de cada cajón lo que llamamos un dato; cada cajón se podrá abrir mediante una dirección específica. Administración de la Memoria El esquema más sencillo es el direccionamiento directo, lineal, de la memoria física. Para este tipo de direccionamiento no hay otra planificación más que comprobar si el espacio físico libre de memoria permite cargar el programa y los datos que queremos ejecutar. Este tipo de esquema fue el utilizado en las primeras computadoras para el modo monoprocesamiento. 14 Cuando se comenzó a pensar en mejorar la eficiencia de la CPU, esto llevó a considerar lo que se llama multiprocesamiento. En este contexto la CPU se conmuta entre varios procesos (cada proceso podría pertenecer a un programa diferente); la RAM debe contener a los procesos y, por lo tanto, la RAM va a estar compartida. Los algoritmos de administración de la memoria tienen sentido en el contexto de multiprogramación. Multiprogramación lineal Éste es el esquema más sencillo de la administración y se basa en usar la memoria para cargar la mayor cantidad de procesos posibles. El SO no utiliza otro algoritmo que no sea el de buscar un espacio lo suficientemente grande como para que quepa el proceso. En la primera carga de la memoria se puede llegar a aprovechar valores muy cercanos al 100 % de la memoria, pero a medida que el SO debe cambiar los procesos para su futura ejecución, y ya que los procesos no ocupan todos el mismo lugar, van quedando huecos cada vez más pequeños y distribuidos, lo que los convierte en inútiles (los procesos deben cargarse en posiciones contiguas de la memoria). A este problema se lo llama Fragmentación Externa y puede dejar espacios muy grandes de la memoria inutilizables. La forma de solucionar esto es efectuando la compactación de la memoria. En este modelo el SO debe analizar todo el espacio de memoria e ir reubicando procesos de manera de ir acumulando esos huecos de manera de volverlos útiles. Este procedimiento ocupa mucho tiempo de CPU, haciendo que el rendimiento del sistema disminuya notablemente. Multiprogramación por páginas La primera forma de solucionar la fragmentación externa es dividiendo el espacio de memoria RAM en subespacios (frames), todos de la misma capacidad. Se suele denominar Frames a los marcos de página, es decir, al espacio físico sin datos que conformará la página (datos). Este esquema tiene, como todos, sus ventajas y sus desventajas. Como ventajas principales están: a) Un direccionamiento jerarquizado, tipo Árbol b) La posibilidad de extender la memoria RAM a través de un concepto denominado Memoria Virtual, en el que se usa el disco como extensión de la RAM disponible. Como desventajas se pueden citar: a) Que el direccionamiento es más complejo y requiere apoyo de la arquitectura de la CPU b) Dependiendo del tamaño de las páginas, aparecen en la última página de un proceso zonas libres (a menos que el proceso ocupe exactamente un múltiplo entero de la capacidad de las páginas), que se denomina fragmentación interna, y que, estadísticamente, podría estimarse en la mitad de la capacidad de una página. c) Aparece el concepto de Dirección Lógica, que debe ser transformada a una dirección física, como vemos en el siguiente ejemplo: 15 En este esquema, los procesos pueden cargarse en zonas de memoria que no sean contiguas, la que sí debe ser secuencial es la tabla de páginas. Dentro del programa, cada dirección lógica está constituida por un número de página y una dirección relativa dentro de la página (desplazamiento). La tabla de páginas está implícita en los PCB, en la forma de registro Base y registro Límite. Existen mecanismos dentro de la arquitectura del procesador que verifican que las direcciones físicas obtenidas sean congruentes con esta tabla de página, para evitar que el Program Counter esté direccionado a una dirección de memoria errada. El esquema siguiente representa el modo de direccionamiento del Pentium con un poco más de precisión. 16 Algoritmos de Reemplazo de Páginas Utilizando el concepto posible dentro de paginación que era la memoria virtual, se presenta el problema de una memoria RAM escasa extendida por las páginas ubicadas en el disco. El problema implica saber qué páginas de la RAM puedo borrar cuando necesito cargar páginas del disco. El SO, cuando termina de ejecutar las instrucciones de una página, necesita continuar con la siguiente en forma secuencial. Ahora bien, una vez que termine de ejecutar las instrucciones de esta nueva página, las preguntas son: qué página va a necesitar, y si esta página ya está cargada en la RAM o hace falta borrar una de las páginas usadas para cargar la necesaria del disco (Fallo de Página). Detallaremos a continuación algunos algoritmos útiles para los fallos de página: a) Reemplazo sencillo tipo FIFO (First In First Out). Es el algoritmo más sencillo y barato de implementar, ya que sólo lleva una cola de las páginas a reemplazar, cuyo orden en principio es aleatorio. Este esquema es el que, en general, produce más fallos de página, y por lo tanto el menos eficiente. b) Reemplazo óptimo. Es el mejor algoritmo en cuanto a la cantidad de fallos de página, pero es muy difícil de implementar. Se etiqueta cada página con un número equivalente a la cantidad de instrucciones del programa en el que se hace referencia por primera vez a esa página. El algoritmo implica cambiar las páginas de acuerdo al menor número, es decir, la página a la que se hace referencia en la menor cantidad de instrucciones del programa. Para que este algoritmo pudiera ser implementado, el programa no debería tener saltos, o los saltos ocurrir dentro de páginas contiguas a la actual, lo cual es irrealizable en programas convencionales. 17 c) La menos recientemente usada (variable tiempo)(LRU: Least Recently Used). Es un algoritmo que se basa en tiempo que ha pasado desde que esa página se usó. Es probable que las páginas que han sido usadas recientemente, no se usen enseguida. La implementación de este algoritmo implica mantener una lista histórica de cuándo fueron usadas las páginas solicitadas por cada proceso. d) La que más recientemente se usó (variable tiempo)(Most Recently Used) El algoritmo es el contrario al anterior y, a pesar de ello, es mejor que el de reemplazo sencillo. e) Menos frecuentemente usada (variable frecuencia)(LFU: Least Frequently Used). El algoritmo anterior se basaba directamente en el tiempo transcurrido desde el último uso de una página en particular. En este algoritmo, lo que se verifica es la cantidad de veces que fue solicitada históricamente esta página. Ante un fallo de página, la que se reemplaza es la que menos veces fue utilizada por el programa. f) Más frecuentemente usada (variable tiempo)(Most Frequently Used) Como en el caso de tiempos, este algoritmo implica considerar el cambio de la página que más se haya usado. Teniendo en cuenta la localidad de las referencias, este algoritmo debería ser peor que el anterior. Existen otros métodos de intercambio de páginas más o menos eficientes que suponemos se tratarán con más detalle en la materia destinada a SO. Caso práctico del uso de los algoritmos vistos Tomaremos el caso de un proceso que utiliza secuencialmente las páginas siguientes: 7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 0, 1, 7, 0, 1 Supondremos además que sólo disponemos de tres marcos para completarlos con los datos de esos números de página. FIFO Hay 19 pedidos de páginas, a continuación se grafican los fallos de página. Con este algoritmo, de 19 pedidos hubo 12 fallos. Tener en cuenta que esto es una cola en donde los nuevos datos entran por la parte superior y los últimos datos se pierden. ÓPTIMO 18 En este algoritmo se reemplaza la página que no se usará durante el mayor período de tiempo que sigue, es decir, el SO debe “adivinar” la secuencia de páginas que usará el programa. LRU Este algoritmo se podría llevar a cabo asociando cada posición de la tabla de páginas a un contador que se vaya incrementando con unidades de tiempo predefinidas. La página que se cambia es la que tiene el mayor número asociado. En el caso de no haber fallo de página, el contador asociado a ella se vuelve a 0. LFU En el ejemplo, al querer aplicar el algoritmo, nos encontramos con que dos de las páginas han sido usadas igual número de veces, en este caso adoptamos cambiar la que más lejana en el tiempo estuvo, o sea que, en realidad, hemos hecho una mezcla de dos algoritmos. Multiprogramación por segmentos Conceptualmente se puede entender como un conjunto de espacios de longitud variable. La ventaja de esta imagen sobre el enfoque de paginación estriba en que se puede proporcionar protección a nivel de byte si fuera necesario. Para el Pentium existe una referencia al segmento de 16 bits y un desplazamiento de 32 bits. Dos de los bits de la referencia al segmento se utilizan para protección y los 14 restantes para especificar el segmento. En total se dispone entonces de 246, o 19 sea 64 TeraBytes; el espacio de direcciones físicas utiliza 32 bits, lo que da una memoria de trabajo de 4 GigaBytes (límite de los SO actuales). Algoritmos para el manejo de la memoria secundaria o virtual Para mejorar la eficiencia de la transferencia de datos en el disco, se pueden usar 3 algoritmos básicos: a) FCFS (First Come First Served). El archiconocido método de que el primero que llega es el primero que se atiende. b) SSTF (Shortest Seek Time First). Con este algoritmo se organiza la cola de pedidos al disco de manera que estén ordenadas por cercanía a la posición que se toma como inicial. c) SCAN (Barrido). Este método trata de mantener la dirección (ascendente o descendente) que llevaban las cabezas al momento que consideramos como inicial y lee sólo en esa dirección, cuando vuelve al principio no lee ni escribe. d) C-SCAN (Barrido circular) Este algoritmo lee tanto en el camino de pistas ascendente como descendente Estos algoritmos tratan de minimizar las traslaciones entre pistas de las cabezas que era un tiempo relativamente largo y que afectaba fundamentalmente a la velocidad de transferencia de datos. En el siguiente gráfico vamos a sacar los tiempos de respuesta suponiendo que un proceso haya pedido datos de las siguientes pistas: 50, 98, 183, 37, 122, 14, 124, 65, 67 20 Memoria Caché Glosario Capacidad: normalmente se expresa en términos de Bytes, aunque dependiendo de la jerarquía de memoria se puede expresar en palabras. Lamentablemente nunca se pusieron de acuerdo en cuanto a la definición de cantidad de bits de una palabra. En general se toma como la unidad “natural” de la jerarquía de memoria de la que se está hablando, y de la CPU asociada. Hay palabras de 32, 64, 128 bits. Tiempo de acceso: es el tiempo que tarda en realizarse una operación de lectura/escritura completo, es decir: el tiempo que transcurre desde el instante en que se presenta una dirección a la memoria hasta que el dato (Byte o palabra) está disponible para su uso en el bus de datos. 21 Tiempo de ciclo de memoria: es el tiempo de acceso más los tiempos de las señales de control involucradas. Ubicación: indica si la memoria es interna o externa al computador. La memoria interna suele identificarse con la memoria principal (RAM). El punto de vista para determinar la ubicación podría ser también la CPU; en este caso la memoria interna estará constituida por los registros y la memoria caché, y la memoria externa pasaría a ser la RAM. Unidad de transferencia: indica cuántos bits o Bytes se transfieren en un ciclo de lectura/escritura. Algunas veces coincide con la palabra, pero en otros casos es mayor (256 bits o más). Unidades direccionables: en la mayoría de los casos, coincide con la palabra; en algunos, especialmente los registros, se permite direccionar a nivel de bits. En cualquier caso, la relación entre las unidades direccionables A y el número de unidades direccionables es 2A=N. Velocidad de transferencia: es la velocidad a la que se pueden transferir datos (actualmente se habla de bits por el tipo de transmisión usado). Se puede utilizar la siguiente relación: TN TA N R TN = tiempo medio de lectura/escritura de N bits. TA = tiempo de acceso medio por bit N = número de bits bits R = velocidad de transferencia en seg Jerarquía de memoria Memoria en tarjetas/CPU Registros Alto costo por bit Muy baja capacidad Muy rápida Accesos CPU a L2 Muy bajo costo por bit Alta capacidad Lenta Accesos CPU a L1 Caché RAM Almacenamiento Discos fuera de tarjetas Almacenamiento WORM fuera de línea Existe un compromiso entre las tres características clave (costo, capacidad y tiempo de acceso). A menor tiempo de acceso, mayor costo; a mayor capacidad, menos costo por bit; a mayor capacidad, mayor tiempo de acceso. Existe una solución de compromiso que implica tener memorias en diferentes “niveles” (Levels), estos se enumeran con valores en incremento de acuerdo a la lejanía relativa a la CPU; así el L1 es la memoria más cercana a la CPU y la L3 la más lejana. Este esquema implica tener varios niveles ordenados por capacidad-velocidad; en este caso, L1 es la más veloz, aunque de menor capacidad. 22 La última columna de la tabla de jerarquías supone que los acccesos a memoria lenta son cada vez menos frecuentes. Esto es moderadamente verdad, siempre que se cumpla la condición de que la ejecución de un proceso sea efectuado genéricamente en pocas páginas de instrucción (o datos); con la disminución de los costos de caché al ser integrados al chip de la CPU esta condición es mucho más frecuente y por lo tanto la eficiencia del uso de caché se nota mucho más. Esta característica de los programas o procesos fue denominada localidad de las referencias (temporal y espacial) y establece que durante períodos de tiempo largos (tiempos de CPU), las agrupaciones de memoria en uso cambian, pero considerando períodos cortos, el proceso tiende a usar las mismas referencias a memoria. Estudios realizados sobre programas realizados en C demuestran que con 8 bloques de caché por proceso, sólo hay fallos de página en menos del 1% de los accesos a caché. En el cálculo de los tiempos de acceso para memorias de dos niveles, debemos considerar el concepto de tasa de aciertos. En una configuración de dos niveles, la tasa de aciertos de un nivel corresponderá a la inversa (Tasa de fallos) del otro nivel. TM H * T1 (1 H ) * (T2 T1 ) TM T1 T2 * (1 H ) H= tasa de aciertos del nivel 1 T1= tiempo de acceso medio al nivel 1 (1-H)= tasa de fallos del nivel 1 (certeza – aciertosL1 = fallosL1) T2= tiempo medio de acceso al nivel 2 TM= tiempo medio de acceso a los dos niveles Si H fuera el 100%, el tiempo medio sería T1; si H fuera 0, el tiempo medio sería T1+T2 Supongamos que el procesador tiene que acceder a dos niveles de memoria: L1 contiene 1K palabras y un T1 = 10 nseg.; L2 contiene 100K palabras y T2 = 100nseg. y H = 0,95. El método de escritura de la caché es WT (write through), lo que lleva a considerar que si debe acceder al L2 debe haber efectuado un ciclo de lectura de L1 para buscar la página y, al no encontrarla, busca en L2; cuando la encuentra debe grabarla en L1 para que la CPU pueda acceder al dato (o instrucción en V.Neumann). Despreciando los tiempos de handshaking, el tiempo medio de acceso será: 0,95 *10nseg (1 0,95)(10nseg 100nseg ) 15nseg Lo que justifica usar diferentes niveles son las condiciones implícitas en el gráfico de jerarquías: L1 pequeña pero extremadamente veloz, L2 más grande y más lenta y L3 mucho más grande y aún más lenta que L2. Una relación posible es un orden de magnitud (10 veces) entre niveles. Prestaciones Para evaluar la cantidad de caché a utilizar se deben considerar el costo y el rendimiento final del conjunto. El costo se calcula como: Ct C1M 1 C2 M 2 M1 M 2 C es el costo por palabra M es la cantidad de palabras a adquirir 23 El nivel 2 es más barato, de modo que interesa conseguir que Ct sea muy próximo a C2. Esto se consigue haciendo que M1 << M2, cosa que se cumple en la realidad. También debemos conseguir que TM sea cercano a T1, lo que se consigue con una caché de gran tamaño. Se deben considerar las dos variables: tiempo de acceso (función de los aciertos) y costo (función del tamaño). Sabiendo que la relación de los tiempos de acceso medio entre la memoria principal y la caché es un número cercano a 50 y que una relación entre tiempo medio del sistema y tiempo de acceso a caché puede ser 10, obtenemos una tasa de aciertos mínima del 82%. La cuestión de la cantidad será función de la localidad de las referencias a memoria, para una localidad entre moderada y fuerte conseguimos tasas de acierto en ese orden con un 20% de memoria caché. Comprobar si se cumplen estas condiciones usando la memoria principal como caché de disco, suponiendo que la relación de costos es 1/200 y la relación de tiempos 1000/1. El cálculo se complica un poco al considerar tres niveles, pero se puede resolver de a dos sistemas por vez, como un sistema lineal. Partimos de que tenemos la certeza de que la página buscada está en alguno de los tres niveles: H1 H 2 H 3 1 Entonces, el tiempo medio (o promedio) de acceso queda: t m H1 * t1 H 2 * t1 t 2 H 3 * t1 t 2 t 3 Reagrupando: tm tt H1 H 2 H 3 t2 H 2 H 3 t3 H 3 H1 H 2 H 3 1 tm tt t2 1 H1 t3 1 H 2 H1 En la actualidad, tenemos microprocesadores con hasta 3 niveles de caché integrados dentro de la CPU, esto permite velocidades en el orden (o iguales) a la del reloj que comanda a la CPU. El problema de la cercanía es un tema físico: si la longitud de la línea(s) de transmisión de datos se asemejan a la longitud asociada a esa onda, el conductor se transforma en una antena que irradia energía al exterior como ondas electromagnéticas (el principio de la radiodifusión), concretamente para frecuencias del orden de los 2GHz, la longitud de onda se calcula como: m 3.108 c seg 1,5.10 1 m 15cm f 2.109 Hz 24 Para que no oficie de antena, la longitud de la línea debe ser alrededor del 10% de , o sea 1,5 cm. Resumiendo lo anterior: no se deben mover señales eléctricas con frecuencias cercanas a los 2GHz a distancias mayores de 1,5cm. La figura siguiente describe la estructura de un sistema de memoria caché/principal. La memoria principal consta de hasta 2n unidades direccionables y cada palabra tiene una única dirección de m bits. La memoria está dividida en bloques (podrían ser páginas) de K palabras por bloque, es decir que hay M bloques (páginas), siendo M = 2n/K. La caché consta de C filas, cada fila contiene K palabras, es decir que en cada fila de la caché entra un bloque de los considerados para la memoria; tiene además unos bits de más como etiqueta, que habitualmente es parte de la dirección del bloque e información sobre modificaciones en el bloque (si fue modificado por el proceso hay que grabarlo a disco antes de eliminarla de la caché, en caso contrario sólo se elimina). Se cumple la condición de que C<<M. En todo momento un subconjunto de bloques de la memoria está copiado en la caché. Unidades de Entrada/Salida Uno de los elementos clave de un computador es el conjunto de módulos de Entrada/salida. Cada módulo se conecta al bus del sistema o a un conmutador central y controla uno o más dispositivos periféricos. Las razones por las cuales un periférico no se conecta directamente al bus son, entre otras: a) Existe una amplia variedad de periféricos con lógicas de funcionamiento diferentes b) A menudo la velocidad de transferencia de datos es mucho menor que la del bus 25 c) Algunos pocos periféricos pueden tener velocidades que superen a la del bus d) Los periféricos usan datos con formatos y tamaños de palabra diferentes a los del computador. Como interfase entre el bus y el dispositivo genéricamente aparece un módulo de entrada/salida. Este módulo, como función, debería compensar la lógica del dispositivo de entrada/salida, los formatos y las velocidades. Las funciones de un módulo suelen incluir, además de las antedichas, el almacenamiento temporal de datos y la detección de errores. El control de la transferencia de datos desde un dispositivo externo hasta el procesador podría involucrar los siguientes pasos: a) El procesador interroga al módulo de entrada/salida para comprobar el estado del dispositivo. b) El módulo devuelve el estado. c) Suponiendo que el dispositivo está preparado para transmitir o recibir datos, el procesador solicita al módulo la transferencia del dato. d) El módulo de entrada/salida obtiene o transmite el dato del dispositivo externo. e) El dato se transfiere desde el módulo al procesador. Con referencia al gabinete, los dispositivos pueden dividirse en internos y externos. Genéricamente, los externos interaccionan con el operador y, a excepción del monitor (video), se pueden considerar como muy lentos. Los internos están más relacionados a los datos y sus velocidades de transferencia, comparados con los de la CPU, también son lentos (pero mucho más rápidos que los externos). Como ejemplo, citaremos el módulo (chip) identificado como 8255, destinado a controlar interfases que manejan datos en forma paralela (8 bits). Este chip es bastante parecido a un microcontrolador: tiene tres puertos de 1 Byte, configurables como entradas o salidas, una salida/entrada de datos de 1 Byte, algunas señales de control y dos líneas para direcciones. El uso más difundido fue el concepto de “mapeado en memoria”. Este concepto implica que los registros internos del módulo (en este caso, el 8255) se hacen coincidir con direcciones físicas de la RAM. Con este método, las diferencias de velocidad se solucionan porque esas posiciones de 26 memoria RAM actúan como un almacenamiento de datos temporal (buffer) que compensa las diferencias de velocidad. En el modo de transmisión, la CPU coloca el dato a transferir en la posición de RAM asignada, habilita al módulo con una señal de control denominada Chip Select (CS) y, a continuación, otra señal de control denominada Write. En el ciclo de lectura, el chip coloca al dato en otra posición de memoria, solicita una interrupción y, cuando ésta es aceptada, la CPU le indica al módulo que está lista a leer el dato con la señal Read; esta última aparentaría no ser necesaria, ya que la CPU puede leer el dato estrictamente con un acceso a memoria, esta señal, en realidad, le sirve al módulo para saber que el dato fue leído y que, eventualmente, puede colocar otro dato en la memoria. Este módulo, en particular, es programable en una cantidad de modos de funcionamiento. Esos diferentes modos se eligen a través de un dato denominado “Palabra de control”, al que se le asigna otra posición de memoria, efectuándose la programación a través de un ciclo de escritura y el conocimiento del estado del chip a través de uno de lectura. El método anterior, denominado E/S programada tiene como ventaja la sencillez de la implementación, pero la desventaja de emplear mucho tiempo en ciclos de lectura/escritura de RAM para obtener el/los datos necesarios. Como ejemplo, supongamos que el proceso en ejecución requiere como operando a un valor codificado en PuntoFlotanteDoblePrecisión; esto requiere leer 64 bits (8Bytes) y también 8 ciclos de lectura del módulo de E/S. Mientras la CPU carga el/los datos necesarios no puede hacer otra cosa que esperar respuesta del módulo. Como las velocidades de E/S son considerablemente menores que la RAM y la CPU, la solicitud y carga del dato requiere mucho tiempo de inactividad de la CPU. En computadoras personales, esto es un inconveniente menor ya que se resume a que el operador obtenga una baja velocidad y use el tiempo de inactividad de su CPU en penControladores sar cómo puede hacer para de interrupciones esclavos comprarse una PC más rápida. Si cambiamos nuestro punto de Dispositivo externo 00 IRQ 0 vista a los negocios, ese tiempo IRQ 1 Dispositivo externo 01 INT representa dinero quieto en funIRQ 2 Dispositivo externo 02 IRQ 3 ción de dinero moviéndose y, IRQ 4 por supuesto, el dinero moviénIRQ 5 dose genera ganancias así que IRQ 6 hay que hacer más eficiente al Dispositivo externo 07 Controlador IRQ 7 de interrupciones sistema. Dispositivo externo 08 Dispositivo externo 15 Dispositivo externo 56 Dispositivo externo 63 IRQ 0 IRQ 1 IRQ 2 IRQ 3 IRQ 4 IRQ 5 IRQ 6 IRQ 7 IRQ 0 IRQ 1 IRQ 2 IRQ 3 IRQ 4 IRQ 5 IRQ 6 IRQ 7 maestro INT IRQ 0 IRQ 1 IRQ 2 IRQ 3 IRQ 4 IRQ 5 IRQ 6 IRQ 7 INT INTR CPU Para mejorar este rendimiento, se pensó en agregar un controlador llamado de interrupciones y hacer los pedidos a través de él. En este esquema, cuando la CPU necesita un dato ejecuta una orden de lectura a una dirección. La línea de lectura puede ir directamente al controlador de interrupciones o al INT 27 controlador y a la RAM. La CPU sigue con otras tareas mientras el controlador de interrupciones prepara al módulo de E/S correspondiente a la dirección suministrada por la CPU. Cuando el sistema de E/S (módulo + hardware correspondiente) está listo, lo comunica al controlador de interrupciones (CI) por una línea de control de las 8 que dispone el CI. Dependiendo de la línea solicitante, el CI las ordena por prioridades y solicita a la CPU la interrupción por la línea IntR, junto con la identificación del periférico que requiere atención. El SO determina si se va a atender o no esa interrupción a través de “máscaras” de comparación. Las “máscaras” no son otra cosa que datos contra los cuales se compara la identificación del periférico que requiere atención con algún orden de prioridad preestablecido. El 8259 de Intel (CI) se puede cablear en modo árbol para manejar hasta 64 dispositivos de E/S. La programación normal determina que la línea IRQ 0 es más prioritaria que la INT 7, por lo tanto en la figura tienen prioridades relacionadas en forma inversa a la identificación del dispositivo. En el caso de que la interrupción sea aceptada, el SO entra en un ciclo de cambio de proceso guardando los registros de la CPU y los datos necesarios para retomar el proceso actual y cuando están preservados, solicita al CI la dirección correspondiente del vector de interrupciones para seguir el proceso de lectura/escritura del dato. Aparentemente todo este proceso es más largo que el anterior y es cierto, pero usando interrupciones, la CPU puede hacer otras cosas entre el pedido del dato y la lectura del mismo. El resultado es un aumento en la eficiencia de la CPU y por ello de todo el conjunto. El manejo de E/S por interrupciones no soluciona el problema de tener varios ciclos para leer un dato compuesto de varios Bytes, una alternativa más moderna es usar una CPU ayudante denominada Direct Memory Access (DMA). Este chip se interpone en la comunicación de los datos con periféricos que habitualmente requieren datos largos y tiempos de respuesta menores a sus transferencias a la memoria RAM, como por ejemplo la placa de red, la controladora de sonido y el disco rígido, entre otros posibles. La ventaja de agregar esta CPU ayudante es que la CPU principal le comunica el tipo de dato que necesita y la dirección inicial de memoria en que hay que ubicarlo. El DMA se encarga de la tarea rutinaria de ir cargando las posiciones consecutivas de memoria hasta completar el dato y avisarle a la CPU la disponibilidad del mismo a través de una interrupción. En este procedimiento, la CPU sólo actúa para pedir el dato y para obtenerlo. En el caso de existir un solo Bus de Datos, van a tener que compartirlo entre la CPU y el DMA, lo cual hace que la eficiencia del sistema se incremente comparado al uso sin DMA, pero no lo deseable. La alternativa más costosa es construir otro Bus que se llamará de E/S conectado directamente al DMA, con lo cual los procesamientos de E/S se delegan al DMA y los procesos más rápidos a la CPU, cada una de ellas con su propio canal de comunicaciones. El último concepto relacionado con las operaciones de E/S es el denominado “Canal de E/S”. El módulo de E/S tiene una zona de memoria propia, Buses propios y maneja un repertorio reducido de instrucciones (es capaz de ejecutar procesos) tendientes a las comunicaciones con los dispositivos de E/S, desde un punto de vista es otra computadora cuya tarea es comunicar datos a una computadora más rápida (esquema de cliente-servidor). El rendimiento, la amplitud de datos y la seguridad obtenida en el uso de la WideWorldWeb (internet) hizo que se pensara en esquemas distribuidos aún dentro del mismo gabinete y ahora existen configuraciones de E/S (FireWire, Infiniband, USB) que usan protocolos de comunicaciones similares a los usados en Internet, aunque con menos capas y mucho más rápidos, llegándose en algunos casos de uso de fibra óptica a 28 velocidades de transferencia de 30 Gb/seg y a 300m de distancia, cuando por cobre el PCI llega a 1Gb/seg a distancias de pocos centímetros. Interrupciones en el Pentium Hay dos tipos de eventos que hacen que el Pentium suspenda la ejecución del flujo de instrucciones en curso y responda al evento: las interrupciones y excepciones. Una interrupción se genera por una señal del hardware y puede ocurrir en cualquier momento. Una excepción se genera desde el software y es provocada por la ejecución de una instrucción. Hay dos fuentes de interrupciones y dos fuentes de excepciones. Interrupciones enmascarables: las recibe el procesador por el pin INTR. El procesador no atiende una interrupción enmascarable a no ser que un indicador especial lo habilite. Interrupciones no enmascarables: el pin se denomina NMI. El procesador no tiene forma de evitar atender a tales interrupciones (por ejemplo: RESET). Excepciones detectadas por la CPU: se producen cuando el procesador encuentra un error mientras intenta ejecutar una instrucción. Excepciones programadas: hay instrucciones que generan una excepción (por ejemplo: fallo de página, depuración, etc.) El procesamiento de las interrupciones usa una tabla de vectores, cada tipo de interrupción tiene asignado un número que se usa para identificarla. Este número se usa como entrada a la tabla de vectores de interrupción. La tabla contiene 256 datos de 32 bits cada uno, que representan la dirección (segmento y desplazamiento) de la rutina de servicio a ejecutar. Si se generan más de una interrupción o excepción, la CPU las atiende en un orden determinado por el SO. ID 0 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 DESCRIPCIÓN Error al dividir; desbordamiento de división o división por cero Excepción de depuración. Incluye fallos e interceptaciones relacionadas con la depuración de procesos. Punto de parada causado por la instrucción INT3 Desbordamiento detectado por INT0 Instrucción fuera de límites de la memoria No definido Dispositivo no disponible Doble fallo: dos interrupciones ocurres durante la misma instrucción Reservado Segmento en estado de tarea no válido Segmento no presente Error en la pila Violación de protección general Fallo de página Reservado Error de coma flotante Acceso a una palabra almacenada en dirección de Byte impar o doble palabra en una dirección que no sea múltiplo de 4 Verificación de la CPU (Watchdog) 29 19-31 2 32-255 Reservados Interrupción no enmascarable Vectores de interrupción del usuario (señal INTR) Las dos últimas son interrupciones y el resto excepciones. Cuando se produce una interrupción y es atendida tiene lugar la siguiente secuencia de eventos: 1) Si la transferencia supone un cambio de nivel de privilegio, los contenidos del registro del segmento de pila y puntero de pila ampliado se introducen en la pila. 2) El valor actual del bit EFLAGS se introduce en la pila. 3) Los indicadores de interrupciones y de trampa se ponen a cero. Ello inhabilita otras interrupciones y la interceptación o modo paso a paso. 4) Los contenidos actuales del puntero del segmento de código y del puntero de instrucciones se guardan en la pila. 5) Si la interrupción viene acompañada de un código de error se guarda también en la pila. 6) Se captan los contenidos del vector de interrupción y se cargan los registros necesarios para la ejecución de la interrupción. 7) La instrucción IRED vuelve a cargar los registros desde la pila para continuar la ejecución anterior a la interrupción. Paralelismo en las instrucciones – procesadores superescalares El término “superescalar” fue usado en 1987 para mencionar a una máquina diseñada para mejorar la velocidad de ejecución de instrucciones escalares y estuvo muy relacionado con los procesadores pensados en esa época, en donde había más limitaciones en la construcción de grandes memorias RAM (internas y externas). Esta limitación llevó a diseñar procesadores que tenían pocas instrucciones diferentes, pero cuya ejecución se podía decodificar y ejecutar en tiempos muy cortos, dándoles el nombre de RISC (conjunto de instrucciones reducido). En la actualidad, un procesador superescalar es aquel que usa múltiples cauces de instrucciones independientes. Cada cauce consta de múltiples etapas, de modo que puede tratar varias instrucciones a la vez (típicamente un programa de computadora debe considerar que tiene sus instrucciones ordenadas en una serie cuya lógica resuelve el problema). El hecho de que haya varios cauces introduce un nuevo nivel de complejidad al permitir varios flujos de instrucciones que se procesan simultáneamente. El procesador superescalar saca provecho de lo que se conoce como “ paralelismo en las instrucciones”, que hace referencia al grado en que las instrucciones pueden ejecutarse en paralelo (independencia de las instrucciones). El procesador capta varias instrucciones a la vez y trata de encontrar instrucciones cercanas que sean independientes entre sí y puedan, por ello, ejecutarse en paralelo (multiplexión de recursos, no del tiempo). Si la entrada de una instrucción no es independiente, esto es que su entrada de datos depende de la salida de otra (precedente), esta instrucción no puede ejecutarse al mismo 30 tiempo o antes de aquella. Una vez que se han identificado tales dependencias, el procesador puede emitir y completar la ejecución de instrucciones en un orden diferente al código original. El procesador puede eliminar algunas dependencias innecesarias a través del uso y renombramiento de algunos de su gran cantidad de registros internos. Para mejorar el valor del “paralelismo de las instrucciones” se puede usar una combinación entre software (el compilador) y hardware. Algunas de las limitaciones que se encuentran para mejorar ese valor son: Dependencia de datos verdadera Dependencia relativa al procedimiento Conflicto de recursos Dependencia de datos verdadera: Supongamos el ejemplo A=A+B C=A La segunda instrucción no puede ejecutarse antes (o durante) la ejecución de la primera porque necesita datos de ella. Los procesadores superescalares pueden atrasar un ciclo de reloj a la instrucción dependiente para poder ejecutarla. Esto, de alguna manera es una multiplexión en tiempo, es decir que una instrucción se ejecuta después que la otra. Dependencia relativa al procedimiento El caso de una bifurcación condicional es una dependencia relativa al procedimiento, mientras no se sepa el valor de la condición no se puede ejecutar la instrucción de salto. Conflicto de recursos Uno de los recursos que aparenta ser poco importante son las comunicaciones a través del bus de datos, si la máquina tiene uno solo, las comunicaciones terminan siendo un cuello de botella para el requerido paralelismo. Este conflicto se resuelve parcialmente disponiendo de varias computadoras virtuales (o reales) en una configuración de red, siempre y cuando la comunicación de esta red sea mucho más veloz que las comunicaciones internas de datos de cada nodo. Implementación superescalar 31 La figura representa gráficamente el proceso de ejecución de instrucciones en una CPU de este tipo. El programa a ejecutar consiste de una secuencia de instrucciones, tal como sale del compilador. El proceso de captación de instrucciones tiene una lógica para predecir saltos y genera un flujo dinámico de instrucciones que envía a una ventana de ejecución. En esta ventana, las instrucciones no forman un flujo secuencial sino que están estructuradas de acuerdo a sus dependencias de datos verdaderas. Aquí podrían aparecer instrucciones “fantasma”, necesarias para completar una lógica de programación crítica (PERT). Las instrucciones se ejecutan y se desechan los resultados irrelevantes o de tareas fantasma y se reordena secuencialmente el flujo de salida. Los procesadores de Intel han ido modificando su estructura de escalar (80486), modesta arquitectura superescalar (Pentium: dos unidades independientes ALU para enteros), hasta ser completamente superescalares (Pentium Pro). Arquitectura IA-64 Esta soporta desde el hardware el paralelismo de instrucciones y es bastante diferente a las aproximaciones seguidas por la historia de lo superescalar. Como siempre, está acompañada de la tecnología que permite grandes cantidades de registros y caché de hasta tres niveles integradas dentro del chip. Tiene múltiples unidades de ejecución completas, siendo hasta ahora cuatro, se piensa que es posible llegar a ocho. Un resumen que no expresa todo su potencial es que ahora las instrucciones se ejecutan con predicado, esto es que los ciclos de saltos condicionales se ejecutan completos: ambas ramas llegan a sus resultados y luego se comprueba cuál de las ramas ejecutadas sirve como dato, la otra se desecha. La teoría de hacer y luego comprobar se aplica también en el caso de instrucciones de control y datos. Referencias bibliográficas: 32 Sistemas Operativos Modernos, Adrew S. Tanenbaum. Editorial Prentis Hall. Organización y Arquitectura de Computadores, William Stallings. Editorial Prentis Hall. 33 Programación con assembler de un PIC 16F628/872 Los genéricamente llamados procesadores tienen dos ramas que difieren en el modo en que se comunican con el exterior: los procesadores propiamente dichos tienen patas definidas para entrada, salida, datos, direcciones y alimentación conocidas y fijas en cuanto a sus funciones. Las comunicaciones con el exterior son síncronas y permanecen en un estado lógico tiempos muy cortos, en algunos casos la duración de una instrucción (en el orden de algunos nanosegundos), debido a la gran cantidad de información que se espera que manejen. Los procesadores que llamaremos “controladores” se diferencian por tener patas de comunicación que pueden variar sus funciones durante la ejecución de un programa. Pueden ser durante unos ciclos de reloj entradas, luego pasar a ser salidas e incluso permanecer en un estado lógico inamovible durante mucho tiempo. El procesador al cual nos dedicaremos pertenece a esta última clase, lo fabrica la firma Microchip y llevan genéricamente el nombre de PIC y el núnero de modelo del que se trate, en este caso el 16F628. El primer número hace referencia al grupo dentro de la clase: existen los 12, 14, 16 y 18, cuanto mayor el número, mayor la potencia de procesamiento. A excepción de los de alta gama, todos tienen un grupo reducido de instrucciones (RISC), que ejecutan en lo que se denomina “ciclo de instrucción” y que lleva cuatro ciclos del reloj. Con estos controladores se han fabricado desde boyas hasta la lógica de un automóvil. Son muy versátiles en cuanto a la frecuencia de reloj y la cantidad de patas de comunicación con el exterior, además de las funciones internas que son capaces de manejar que cubren desde comunicaciones seriales, paralelas, conversores analógico-digitales, conversores de frecuencia, modulación, etc. etc. Todos tienen la esctructura de una máquina de Harvard, es decir una memoria (más el bus correspondiente) para instrucciones y una memoria para datos, esto le permite instrucciones de 14 bits contra 8 bits de la memoria de datos. El modelo elegido tiene capacidad de almacenar programas de hasta 2000 líneas en una memoria tipo Flash (de allí la F del nombre), una RAM organizada en cuatro bancos de 128 posiciones cada uno en donde residen los registros de control y algunas posiciones para ocupar con variables del programa y una zona de memoria EEPROM (64 Bytes) en donde colocar datos cuyos valores se quiere preservar ante una falta de energía eléctrica (valores de configuración o promedios, etc.). El chip tiene 28 patitas, 24 son de entradas/salidas organizadas como tres grupos (PORTS) de 1 Byte y las restantes son para la alimentación y reloj externo. Cada pata tiene la posibilidad de ser entrada o salida digital o analógica, entrada de eventos exteriores que se pueden contar, entrada del programa, interrupción, etc. Como promedio cada pata puede tener dos funciones programables desde el programa a ejecutar (cambiables en tiempo de ejecución). Está dotado de tres contadores identificados como TMR0, TMR1 y TMR2, hasta ocho interrupciones concatenadas, 10 eventos diferentes para provocar inerrupciones, conversor A/D de 10 bits, comunicación tipo Master o Master/Slave y un circuito de detección de fallos en la alimentación eléctrica que permite pasarlo al modo “sleep”, de bajo consumo y conservación de los datos (debe tener una alimentación extra por batería tipo litio). Sobre la familia de controladores PIC existe una infinidad de documentación, programas, ensambladores y simuladores que dejan a cualquier apunte como el presente como una simplísima introducción al tema. Es justamente eso lo que pretende este apunte: hacer una introducción al lenguaje ensamblador de programación. 34 Los lenguajes de programación tratan de simplificar la vida del programador a medida que incrementan su nivel. El nivel de máquina, donde hay que escribir cada instrucción con los códigos de operación y los argumentos en secuencias de unos y ceros, resulta ser el más complicado; a este nivel le sucede el assembler, en donde cada instrucción se identifica por una cantidad de letras (tres o cuatro) y los argumentos escritos en numeración decimal o hexadecimal; a estos le suceden los de mayor nivel, en donde la programación ya acepta lenguajes matemáticos y sintácticos simples, cuyo parecido al lenguaje convencional es cada vez mayor. El reloj Tiene un circuito interno que puede oficiar de reloj con sólo dos componentes externos que pueden oscilar entre 100KHz y 1MHz aproximadamente, este reloj sencillo es poco preciso, variando con la tensión de alimentación, la temperatura, la variación de valor de los componentes, etc. Para osciladores más precisos se deben usar cristales y se pierden dos patas del controlador. Este controlador puede funcionar con frecuencias de reloj desde 0Hz (parado) hasta 20 MHz. Estos extremos de frecuencias darían tiempos de instrucción entre infinito y 200 nanosegundos. Como reloj interno para contar tiempos se usa el tiempo de instrucción que equivale a cuatro ciclos del reloj. Timer cero (TMR0) Como habíamos dicho antes, los temporizadores son, en realidad, contadores de eventos. Si estos eventos se realizan en tiempos iguales, la cuenta de estos implicaría un tiempo conocido. Este timer en particular se puede configurar como contador de eventos externos, que entrarían por una de las patas, o contador de eventos internos, provenientes de los tiempos de instrucción. También puede programarse lo que se denomina “prescaler”, que es en realidad un divisor programable en 8 etapas que van desde 1:2 a 1:256. El registro usado para poder configurar los tiempos es el TMR0, de 8 bits, lo que permite divisiones de hasta 256 en pasos de a 1. La máxima división que obtendríamos usando el prescaler y el registro es de 1:65536. La salida es un bit (flag) del registro INTCON, denominado TMR01F (bit 2 del INTCON). Este flag, que habitualmente está en 0, cambia a 1 cuando el registro TMR0 pasa del valor FFh a 00h (OVERFLOW). Si se configuran las interrupciones, este temporizador puede generar en el overflow una interrupción. Timer uno (TMR1) Las diferencias con el timer 0 son las siguientes: el prescaler tiene 4 rangos configurables de 1:1 a 1:8, el registro TMR1 pasa a ser de 16 bits, pudiendo cargarse en 2 partes como TMR1L y TMR1H. El timer este no genera interrupciones y su cuenta máxima será de 524288 Timer dos (TMR2) Muy parecido a los otros dos, salvo por la estructura, que en este caso tiene un prescaler configurable entre 1 y 16 en tres pasos, el registro TMR2 de 8 bits y un postscaler configurable entre 1 y 3, también en tres pasos. Este Timer puede generar interrupciones y su cuenta máxima será por 12288. PORTA Tiene 6 patitas configurables como entradas digitales o analógicas o salidas digitales, esto se realiza a través del registro TRISA (bits 0 al 5) y se configuran con el ADCON1 en lo referente a digitales/analógicas. 35 PORTB Tiene 8 patitas configurables como entradas/salidas en ambos casos digitales. Se pueden habilitar unas resistencias de “pull up” internas, con lo cual el estado lógico sin conexión al exterior serán 1’s. Otra característica muy interesante de este puerto es que las patitas RB7:RB4 se comparan en cada instrucción con el valor anterior y, de haber cambios, puede generar una interrupción. PORTC Compuesto de 8 patitas bidireccionales configurables. Este, a diferencia del anterior, no puede generar interrupciones y a diferencia del PORTA las entradas/salidas son siempre digitales. INTERRUPCIONES Todos estos controladores tienen la capacidad de manejar interrupciones, si bien mucho más simples que lo explicado para microprocesadores. El modelo que estamos estudiando acepta diez causas de interrupción, de las cuales sólo nos dedicaremos a tres: a) Cambios en las entradas de mayor peso en el puerto B (bits 7 a 4) b) Overflow en el registro del Timer 0 c) Oiverflow en el registro del Timer 1 d) Overflow en el registro del Timer 2 El bit 7 del registro INTCON, denominado GIE (Global Interrupts Enable), habilita (en 1) las interrupciones o las inhabilita (en 0), más allá de que estén habilitadas por los bits correspondientes a cada una de ellas. El controlador, cuando acepta una interrupción, guarda el contenido del PC (program counter) en una zona de memoria RAM e inhabilita las interrupciones cambiando de valor a GIE; es decir que sólo puede atender a una interrupción por vez. Al volver del programa de atención a la interrupción con RETFIE, habilita nuevamente las interrupciones y carga el PC con el valor guardado en memoria, así puede continuar con la ejecución del programa principal. Hay que tener presente que el controlador no guarda otros valores de registros que no sea el PC, si el procesador necesita otros valores de configuración o registros (sobre todo el W) que pudieron ser modificados por la subrutina de interrupción, corresponde al programador incluir en esa subrutina de atención, loos pasos necesarios para guardar valores importantes del programa principal. Un ejemplo de las instrucciones necesarias para esta operación está explicitada en el ejemplo de Tips & Tricks, más adelante en este apunte. Además del GIE deben estar habilitados los flags correspondientes a cada una de las interrupciones que queramos atender: a) Se habilita con el flag RBIE (bit 3 del INTCON) b) Se habilita con TMR0IE (bit 5 del INTCON) c) Se habilita con TMR2IF (bit 1 del PIR1) Como el controlador tiene un solo vector de interrupciones (ubicado en 04h), desde esta dirección debemos ir a una primera subrutina en la que determinaremos cuál es el flag que nos indica la 36 causa de la interrrupción y desde allí redireccionar a la subrutina de atención de esa interrupción en particular. La primera instrucción debe cambiar el flag de la interrupción para evitar recursividad en la misma. Alguien podría suponer que la rutina a ser atendida puede seguir a la dirección 04h y es cierto, lo que sucede es que si por algún motivo hay que modificar esa rtuina, debe correrse todo el programa principal para hacer lugar a las instrucciones nuevas. Como la compilación se hace con direcciones absolutas hay que recompilar todo nuevamente. Como regla general, para programar en assembler conviene dividir el problema en partes e ir implementando cada una con una subrutina de pocas instrucciones que probaremos y repararemos más fácil que encontrar el problema en un programa extenso. No siempre se puede, pero conviene tratar de hacerlo. Una vez que se tienen las subrutinas probadas, el programa principal va llamando en el orden previsto a cada una de ellas para obtener un resultado (función) o para trabajar variables o tiempos sin necesitar un resultado numérico (subrutinas). SET DE INSTRUCCIONES Y ENSAMBLADOR (MicrochipASseMbler) ***En el Apéndice A se puede ver la configuración paso a paso del MPLab. Un programa para este controlador tiene habitualmente tres partes. La primera, que es la configuración del ensamblador, tiene las siguientes instrucciones: a) RADIX: le indica al ensamblador en qué tipo de numeración se va a trabajar por defecto, y pueden ser DEC, numeración decimal, HEX, numeración hexadecimal, o BIN, numeración binaria. Lo normal es que el ensamblador entienda numeración hexadecimal, si durante el transcurso del programa queremos poner valores en otra numeración bastará anteponerle al valor las letras d, h o b, y el valor entre comillas simples. b) ORG: le indica al ensamblador el número de la línea en la que debe comenzar el programa. En estos controladores es común encontrar la instrucción ORG 5, que significa que la primera línea de programa va a estar en la dirección 5, y esto a su vez, indica que el programa no va a usar las interrupciones cuyo vector está en la dirección 4 de memoria de programa. c) EQU: asigna a una etiqueta un valor que se tomará como la dirección de esa etiqueta. Es cómodo asignarle a los registros más usados la dirección que efectivamente tienen en memoria para recordar solamente el nombre del registro en operaciones de lectura o escritura. d) LIST: permite leer un archivo de texto cuya extensión debe ser .inc y que contiene todas las asignaciones de registros y memoria libre para evitarnos el cometer errores de programación. e) START / END: es la instrucción de comienzo/finalización de una compilación. Habitualmente ubicada antes/después de las instrucciones del programa y subrutinas asociadas. Debe aparecer una vez cada una en el programa fuente. La segunda es el listado de instrucciones en el orden que se supone resolverá el problema. 37 La tercera son las subrutinas o funciones asociadas al programa, las cuales empiezan con una etiqueta que las identifica y terminan con algunas de las variantes de RETURN. SET DE INSTRUCCIONES Todas las instrucciones pueden tener una etiqueta (Label) que las identifique. Las líneas del programa no necesitan numerarse. f identifica una dirección de RAM (registro). k se usa para denotar un valor explícito (literal). b se usa para identificar un bit en particular. d se usa para direccionar el resultado de una operación. Por defecto, es 1, lo que indica que el resultado se guardará en la dirección marcada por f (en el mismo registro que se usa para la operación); en el caso de ser 0, el resultado de la operación se guardará en el registro de trabajo que denominaremos W. 38 ADDLW Sintaxis Operandos Operación Flags Descripción Sumar literal con w Addlw k 0<=k<=255 (w)+k -> w C; DC; Z El contenido del registro w se suma al literal k ADDWF Sintaxis Operandos Operación Flags Descripción Sumar w con f Addwf f,d 0<=f<=127 d= [0,1] (w)+(f) -> destino (d) C; DC; Z El contenido del registrto w se suma al contenido de la dirección f ANDLW Multiplicación lógica entre l yw Andlw k 0<=k<=255 (w).and.k -> (w) Z El contenido del registro w es “andeado” con el literal k ANDWF Multiplicación lógica entre w y f Andwf f,d 0<=f<=127 d= [0,1] (w).and.(f) -> destino (d) Z El contenido del registro w es andeado con el contenido de f Sintaxis Operandos Operación Flags Descripción BCF Borrar un bit de la posición f Sintaxis Operandos Operación Flags Descripción Bcf f,b 0<=f<=127 ; 0<=b<=7 0 -> (f<b>) BTFSS Sintaxis Operandos Operación Flags Descripción Prueba un bit, salta si es 1 Btfss f,b 0<=f<=127 ; 0<=b<=7 CALL Sintaxis Operandos Operación Flags Descripción Llamada a su rutina Call k [label] 0<=k<=2047 (pc)+1 -> tos K -> pc Guarda el contenido actual del PC y pone el contenido Pone a 0 el bit b del contenido en f Si el bit b del registro en f es 0, se ejecuta la siguiente instrucción; si es 1, salta una instrucción Sintaxis Operandos Operación Flags Descripción BSF Sintaxis Operandos Operación Flags Descripción Poner en 1 un bit de la posición f Bsf f,b 0<=f<=127 ; 0<=b<=7 1 -> (f<b>) Pone a 1 el bit b del contenido en f BTFSC Sintaxis Operandos Operación Flags Descripción Prueba un bit, salta si es 0 Btfsc f,b 0<=f<=127 ; 0<=b<=7 CLRF Sintaxis Operandos Operación Flags Descripción Borrar el contenido de f Clrf f 0<=f<=127 00h -> (f) Z El contenido del registro señalado por f pasa 00h Si el bit b del registro en f es 1, se ejecuta la siguiente instrucción; si es 0, salta una instrucción 39 de k en el PC 40 CLRW Borrar el contenido de w COMF Sintaxis Operandos Operación Flags Descripción Clrw Sintaxis Operandos Operación Flags Descripción DECFSZ Sintaxis Operandos Operación Flags Descripción Decrementa f, salta si es 0 Decfsz f,d 0<=f<=127 d= [0,1] (f)-1 -> (destino); salto si 0 INCF Sintaxis Operandos Operación Flags Descripción 00h -> (w) Z El contenido del registro señalado por w pasa 00h Complementa el contenido de f Comf f,d 0<=f<=127 d= [0,1] (f)-1 -> destino Z Decrementa en 1 el contenido de f GOTO Sintaxis Operandos Operación Flags Descripción Salto incondicional Goto k [label] 0<=k<=2047 K -> pc Incrementa contenido de f Incf f,d 0<=f<=127 d= [0,1] (f)+1 -> (destino) Z Se incrementa en 1 el contenido de f INCFSZ Sintaxis Operandos Operación Flags Descripción Incrementa f, salta si es 0 Incfsz f,d 0<=f<=127 d= [0,1] (f)+1 -> (destino) IORLW Sintaxis Operandos Operación Flags Descripción Or inclusivo literal con w Iorlw k 0<=k<=255 (w).or.k -> (w) Z El contenido del registro w es “oreado” con el literal IORWF Sintaxis Operandos Operación Flags Descripción Or inclusivo entre w y f Iorlwf f,d 0<=f<=127 d= [0,1] (w).or.(f) -> (destino) Z El contenido del registro w se “orea” con el contenido de f MOVF Sintaxis Operandos Mueve el contenido de f Movf f,d 0<=f<=127 d= [0,1] MOVLW Sintaxis Operandos Mueve un literal a w Movlw k 0<=k<=255 El contenido del registro f se decrementa en 1, si la operación da 0 salta una instrucción. Salta a la dirección indicada por el literal. El contenido del registro f se incrementa en 1, si la operación da 0 salta una instrucción. 41 Operación Flags Descripción (f)->(destino) Z El contenido del registro f se mueve a otro destino Operación Flags Descripción k-> (w) MOVWF Mueve el contenido de w a la dirección f Movwf f 0<=f<=127 (w) -> (f) NOP No opera Sintaxis Operandos Operación Flags Descripción Nop Sintaxis Operandos Operación Flags Descripción RETFIE Sintaxis Operandos Operación Flags Descripción RLF Sintaxis Operandos Operación Mueve el dato del registro w a la posición f Retorna desde una interrupción Retfie Tos -> pc 1 -> gie Retorna desde una interrupción, habilitando las interrupciones globales. Girar a izquierda a través del acarreo (carry) Rlf f,d 0<=f<=127 d= [0,1] cc registro RETLW Sintaxis Operandos Operación Flags Descripción RRF Sintaxis Operandos Operación El literal k se mueve al registro w Deja pasar un ciclo de instrucción Retorna desde una interrupción con un valor en w Retlw k 0<=k<=255 Tos -> pc k -> (w) Retorna desde una interrupción con un literal en el registro w Girar a derecha a través del acarreo (carry) Rrf f,d 0<=f<=127 d= [0,1] cc registro Flags Descripción C El contenido del registro f es rotado un bit hacia la izquierda usando el carry Flags Descripción C El contenido del registro f es rotado un bit hacia la derecha usando el carry RETURN Vuelve de una rutina SUBLW Sintaxis Operandos Operación Flags Descripción Return Sintaxis Operandos Operación Flags Descripción Resta el literal del contenido de w Sublw 0<=f<=127 d= [0,1] k-(w) -> (w) C, DC, Z Resta el literal del contenido de w y pone el resul- 42 Tos -> pc Retorna al programa principal desde una subrutina tado en w 43 SUBWF Sintaxis Operandos Operación Flags Descripción XORWF Sintaxis Operandos Operación Flags Descripción Resta el contenido de w del registro f Subwf f,d 0<=f<=127 d= [0,1] (f)-(w) -> (destino) C, DC, Z Resta el registro señalado por w del contenido en f XORLW Sintaxis Operandos Operación Flags Descripción O exclusivo entre w y el literal Xorlw k 0<=k<=255 (w).xor.k -> (w) Z El contenido de w se “xorea” con el literal y el resultado va a w O exclusivo entre w y f Xorwf f,d 0<=f<=127 d= [0,1] (w).xor.(f) -> (destino) Z El contenido de w se “xorea” con el contenido de f y el resultado va al destino Esta traducción puede (y de hecho arreglamos varios) tener errores. Conviene verificarlas antes de ponerse de lleno a programar con ellas. 44 Ejemplo de un archivo de texto cuya denominación debe ser p16F628.inc para configurar el ensamblado. LIST ; P16F628.INC Standard Header File, Version 1.01 Inc. NOLIST Microchip Technology, ; This header file defines configurations, registers, and other useful bits of ; information for the PIC16F628 microcontroller. These names are taken to match ; the data sheets as closely as possible. ; Note that the processor must be selected before this file is ; included. The processor may be selected the following ways: ; ; ; ; ; 1. Command line switch: C:\ MPASM MYFILE.ASM /PIC16F628 2. LIST directive in the source file LIST P=PIC16F628 3. Processor Type entry in the MPASM full-screen interface ;========================================================================== ; ; Revision History ; ;========================================================================== ;Rev: ;1.01 ;1.00 Date: Reason: 13 Sept 2001 Added _DATA_CP_ON and _DATA_CP_OFF 10 Feb 1999 Initial Release ;========================================================================== ; ; Verify Processor ; ;========================================================================== IFNDEF __16F628 MESSG "Processor-header file mismatch. ENDIF Verify selected processor." ;========================================================================== ; ; Register Definitions ; ;========================================================================== W F EQU EQU H'0000' H'0001' 45 ;----- Register Files-----------------------------------------------------INDF TMR0 PCL STATUS FSR PORTA PORTB PCLATH INTCON PIR1 TMR1L TMR1H T1CON TMR2 T2CON CCPR1L CCPR1H CCP1CON RCSTA TXREG RCREG CMCON EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU H'0000' H'0001' H'0002' H'0003' H'0004' H'0005' H'0006' H'000A' H'000B' H'000C' H'000E' H'000F' H'0010' H'0011' H'0012' H'0015' H'0016' H'0017' H'0018' H'0019' H'001A' H'001F' OPTION_REG TRISA TRISB PIE1 PCON PR2 TXSTA SPBRG EEDATA EEADR EECON1 EECON2 VRCON EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU H'0081' H'0085' H'0086' H'008C' H'008E' H'0092' H'0098' H'0099' H'009A' H'009B' H'009C' H'009D' H'009F' ;----- STATUS Bits -------------------------------------------------------IRP RP1 RP0 NOT_TO NOT_PD Z DC C EQU EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- INTCON Bits -------------------------------------------------------GIE 46 EQU H'0007' PEIE T0IE INTE RBIE T0IF INTF RBIF EQU EQU EQU EQU EQU EQU EQU H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- PIR1 Bits ---------------------------------------------------------EEIF CMIF RCIF TXIF CCP1IF TMR2IF TMR1IF EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0002' H'0001' H'0000' ;----- T1CON Bits --------------------------------------------------------T1CKPS1 T1CKPS0 T1OSCEN NOT_T1SYNC TMR1CS TMR1ON EQU EQU EQU EQU EQU EQU H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- T2CON Bits --------------------------------------------------------TOUTPS3 TOUTPS2 TOUTPS1 TOUTPS0 TMR2ON T2CKPS1 T2CKPS0 EQU EQU EQU EQU EQU EQU EQU H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- CCP1CON Bits ------------------------------------------------------CCP1X CCP1Y CCP1M3 CCP1M2 CCP1M1 CCP1M0 EQU EQU EQU EQU EQU EQU H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- RCSTA Bits --------------------------------------------------------SPEN RX9 SREN CREN ADEN FERR OERR RX9D EQU EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- CMCON Bits --------------------------------------------------------- 47 C2OUT C1OUT C2INV C1INV CIS CM2 CM1 CM0 EQU EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- OPTION Bits -------------------------------------------------------NOT_RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 EQU EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0003' H'0002' H'0001' H'0000' ;----- PIE1 Bits ---------------------------------------------------------EEIE CMIE RCIE TXIE CCP1IE TMR2IE TMR1IE EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0002' H'0001' H'0000' ;----- PCON Bits ---------------------------------------------------------OSCF NOT_POR NOT_BO NOT_BOR NOT_BOD EQU EQU EQU EQU EQU H'0003' H'0001' H'0000' H'0000' H'0000' ;----- TXSTA Bits --------------------------------------------------------CSRC TX9 TXEN SYNC BRGH TRMT TX9D EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0004' H'0002' H'0001' H'0000' ;----- EECON1 Bits -------------------------------------------------------WRERR WREN 48 EQU EQU H'0003' H'0002' WR RD EQU EQU H'0001' H'0000' ;----- VRCON Bits -------------------------------------------------------VREN VROE VRR VR3 VR2 VR1 VR0 EQU EQU EQU EQU EQU EQU EQU H'0007' H'0006' H'0005' H'0003' H'0002' H'0001' H'0000' ;========================================================================== ; ; RAM Definition ; ;========================================================================== __MAXRAM __BADRAM __BADRAM __BADRAM __BADRAM H'01FF' H'07'-H'09', H'0D', H'13'-H'14', H'1B'-H'1E' H'87'-H'89', H'8D', H'8F'-H'91', H'93'-H'97', H'9E' H'105', H'107'-H'109', H'10C'-H'11F', H'150'-H'16F' H'185', H'187'-H'189', H'18C'-H'1EF' ;========================================================================== ; ; Configuration Bits ; ;========================================================================== _BODEN_ON _BODEN_OFF _CP_ALL _CP_75 _CP_50 _CP_OFF _DATA_CP_ON _DATA_CP_OFF _PWRTE_OFF _PWRTE_ON _WDT_ON _WDT_OFF _LVP_ON _LVP_OFF _MCLRE_ON _MCLRE_OFF EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU H'3FFF' H'3FBF' H'03FF' H'17FF' H'2BFF' H'3FFF' H'3EFF' H'3FFF' H'3FFF' H'3FF7' H'3FFF' H'3FFB' H'3FFF' H'3F7F' H'3FFF' H'3FDF' 49 _ER_OSC_CLKOUT _ER_OSC_NOCLKOUT _INTRC_OSC_CLKOUT _INTRC_OSC_NOCLKOUT _EXTCLK_OSC _LP_OSC _XT_OSC _HS_OSC LIST 50 EQU EQU EQU EQU EQU EQU EQU EQU H'3FFF' H'3FFE' H'3FFD' H'3FFC' H'3FEF' H'3FEC' H'3FED' H'3FEE' Tips & Tricks Transcribo a continuación algunas subrutinas descriptas en las hojas de datos. Se supone que, como están escritas por el fabricante del controlador, funcionan bien y son eficientes en el uso de los recursos. El PC (program counter) de este controlador es de 13 bits y, como debe poder guardarse en RAM (de 8 bits), se compone de dos partes: los 8 bits de menor peso se denominan PCL y los 5 restantes PCLATH. En programas cortos, alcanza con guardar sólo el PCL y eso lo hace automáticamente con el llamado a una subrutina. Si el programa es largo y existe la posibilidad de que estemos ubicados más allá de la posición 128 de la memoria de programa (Flash en este caso), hay que tomar el recaudo de guardar por nuestra cuenta los cinco bits adicionales. Esta subrutina tiene en cuenta esta restricción. Guarda registros importantes cuando atiende a una interrupción: MOVWF SWAPF CLRF MOVWF MOVF MOVWF CLRF ………….. …………. …………. MOVF MOVWF SWAPF MOVWF SWAPF SWAPF RETFIE W_TEMP STATUS, W STATUS STATUS_TEMP PCLATH, W PCLATH_TEMP PCLATH Copia W al registro TEMP Transpone nibbles del reg. STATUS para guardar en W Borra los bits de dirección de bancos. Se posiciona en B0 Copia el reg. STATUS en un registro STATUS_TEMP Sólo se requiere si el programa usa más de un banco de m Copia PCLATH en W Banco de memoria 0 Instrucciones de la subrutina de atención a la interrupción PCLATH_TEMP, W PCLATH STATUS_TEMP, W STATUS W_TEMP, F W_TEMP, W Copia PCLATH en W Restaura el valor de PCLATH Copia los bits del banco de m. usado en el progr. Ppal. Se posiciona en el banco de memoria Transpone nibbles del reg. W_TEMP Reposiciona W al estado original Retorna de la int. Activando las interrupciones globales En esta subrutina deben haberse previsto registros usables de la RAM para ubicar los valores de W , STATUS y PCLATH que aquí se denominaron W_TEMP , PCLATH_TEMP y STATUS_TEMP. Subrutina de exploración para conocer la causa de una interrupción BTFSC CALL INTCON, 1 INTE BTFSC CALL BTFSC CALL INTCON, 2 TIMER INTCON, 0 IPORTB Si el flag de int. Externa es 1, brinca INTE es una etiqueta que identifica la subrutina de atención a las interrupciones externas Si el flag de la int. Por timer está activada, brinca Timer es la etiqueta que identifica a subr. De atención Flag que identifica una int. Por cambios en la puerta B IPORTB es la etiqueta que identifica……. 51 Comprobar igualdad o identificar desigualdad Se usan dos registros en memoria denominados REG1 y REG2 para comprobar su igualdad o eventualmente cuál es mayor (o menor) que el otro. MOVF XORWF BTFSC CALL MOVF SUBWF BTFSS CALL CALL REG1, 0 REG2, 0 ESTADO, 2 IGUAL REG1, 0 REG2, 0 ESTADO, 0 MAYOR MENOR REG1 se carga en W REG1 xor REG2 y se carga en W Si Z = 0 son diferentes y brinca Si Z = 1 son iguales y llama a la subrutina IGUAL REG1 se carga en W (puede tener cualquier valor) Se opera como: REG1 – REG2 y se carga en W Si C = 0 REG2 > REG1 y brinca Si C = 1 REG2 < REG1 y llama a MAYOR Viene de C = 0 REG2 > REG1 y llama a MENOR Subrutina para pasar a diferentes bancos de memoria Parto de la base de que están configuradas las variables STATUS (dirección del registro de estado), RP0 yRP1 (bits 5 y 6 del registro de estado). De acuerdo a las combinaciones de RP0 y RP1 estaremos trabajando en diferentes bancos de la RAM. BANCO0 BANCO1 BANCO2 BANCO3 BCF BCF RETURN BSF BCF RETURN BCF BSF RETURN BSF BSF RETURN STATUS, RP0 STATUS, RP1 STATUS, RP0 STATUS, RP1 STATUS, RP0 STATUS, RP1 STATUS, RP0 STATUS, RP1 Pone a cero el bit 5 del reg. De estado Pone a cero el bit 6 del reg. De estado Retorna en el banco 0 de la RAM Pone a uno el bit 5 del reg. De estado Pone a cero el bit 6 del reg. De estado Retorna en el banco 1 de la RAM Pone a cero el bit 5 del reg. De estado Pone a uno el bit 6 del reg. De estado Retorna en el banco 2 de la RAM Pone a uno el bit 5 del reg. De estado Pone a uno el bit 6 del reg. De estado Retorna en el banco 3 de la RAM Teniendo escrita esta subrutina, cuando hay que cambiar de banco, se usará un llamado a alguna de las entradas, por ejemplo CALL BANCO1. Tablas En el siguiente ejemplo se usa una tabla para que, dado un valor del 0 al 7 colocado en W, se obtenga el código binario para prender un indicador de “siete segmentos” y nos muestre el valor que había en W. Los segmentos están conectados desde la a hasta la g en patas consecutivas del puerto B comenzando con RB0 y hasta RB6. El punto decimal (dp) está cableado a la pata RB7. Hacemos una tabla en papel para ver las equivalencias entre los valores a visualizar y los segmentos que deben prender en esa condición: 52 org 5 15 16 17 18 nada 19 20 21 display 22 23 24 25 26 27 28 29 30 31 movlw call movwf goto 0x03 display portb nada addwf retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw pcl 0x3F 0x06 0x5B 0x4F 0x66 0x6D 0x7D 0x07 0x7F 0x6F 0x00..al..0x09 Decimal 0 1 2 3 4 5 6 7 8 9 PortB 0011 1111 0000 0110 0101 1011 0100 1111 0110 0110 0110 1101 0111 1101 0000 0111 0111 1111 0110 1111 N°instr PC W 15 16 03 Wret ---- Hexa 3F 06 5B 4F 66 6D 7D 07 7F 6F PortB ----- La ventaja de este código es su extrema simpleza, la desventaja (y muy importante) es que las tablas que podemos usar son limitadas porque ocupan posiciones de memoria destinadas al programa. Si se requieren tablas más largas hay que cablear una memoria ROM externa y usar direccionamiento indirecto, es decir que el código se complica bastante y la rapidez también se ve afectada. 53 Programa para multiplicar dos binarios sin signo de 4 bits #include<p16f628.inc> cant equ 0x20 F1 equ 0x21 F2 equ 0x22 aux equ 0x23 org 5 clrf aux movlw 0x04 movwf cant movlw 0x07 movwf f1 movlw 0x06 movwf f2 swapf f2,f sigue 54 movfw cant btfsc status,z goto termina bcf status,c rlf aux,f rlf f2,f btfsc status,c call suma decf cant,f goto sigue suma movfw aux addwf f1,w movwf aux return termina goto termina end Programa para dividir un binario de 8 bits por uno de 4 bits, sin signo #INCLUDE<P16F628.INC> numerador equ 0x20 divisor equ 0x21 indice equ 0x22 resultado equ 0x23 org 5 sigue divide movlw 0x5a movwf numerador movlw 0x05 movwf divisor clrf resultado clrf indice incf indice movfw divisor subwf numerador,0 btfsc status,c goto correr bcf status,c rrf divisor 55 bcf status,c rrf indice btfsc status,c goto termina bcf status,c movfw divisor subwf numerador,1 btfss correr termina s tatus,c goto divide movfw indice addwf resultado,1 goto divide bcf status,c rlf divisor bcf status,c rlf indice goto sigue goto termina end Referencias bibliográficas: Microchip: PIC16F62X datasheet. 56 Apéndice A Configuración del MPLab 8.3 Les preparé esta guía para que puedan configurar el MPLab como yo lo tengo configurado. No quiere decir que sea la mejor ni la única, pero funciona. Se comienza por generar un nuevo proyecto con el ayudante (Project Wizard) Fig. 2 La primera pantalla es como la Fig.2, simplemente una confirmación. En la siguiente deberemos elegir el modelo de PIC, en nuestro caso el 16F628/872. En la próxima seleccionamos la herramienta para compilar, elegimos el MPASM (opción por defecto) En la siguiente seleccionamos un disco, carpeta y nombre para guardar el proyecto. La siguiente sirve para agregar archivos al proyecto. Agregamos el P16F628.INC para usarlo como encabezado (#include…) 57 Finalización del wizard para generar el proyecto. Aceptamos para volver a la pantalla del MPLab. . Por defect o, habrá dos ventanas: a) el contenido actual del proyecto donde debe aparecer entre las carpetas la de encabezado con el archivo INC elegido antes y b) la ventana Salida (output) que aparece vacía. Ir al menú Project, Build options y elegir la solapa para configurar el MPASM Assembler. Tildar la opción para deshabilitar la distinción entre mayúsculas y minúsculas, de forma que si editamos el programa con minúsculas, el ensamblador reconozca el archivo de encabezado que habitualmente está escrito en mayúsculas. Verificar la opción de que trabaje en hexadecimal por defecto. Lo siguiente es elegir la herramienta para depuración (debugger), tildamos MPLabSim. Después de esta elección debería aparecer una barra de herramientas que nos permitirá compilar y correr la compilación, ejecutar instrucción por instrucción, colocar puntos de detención, etc.; es decir serán las herramientas de depuración de nuestro programa (subrutina, función). Lo que deberemos elegir a continuación es la frecuencia del reloj para la simulación: menú Debugger (depuración), Settings (configuración), solapa Osc/Trace: elegir la frecuencia en el valor que usaremos comúnmente: 4 MHz. Se podría elegir la otra 58 opción de frecuencia que es 37 KHz, dependiendo de nuestro diseño. Ahora sigue configurar al PIC, a través de la “palabra de configuración”. Esta palabra puede estar definida en el programa o aquí. Esta palabra establece los siguientes parámetros para el funcionamiento del PIC: a) El tipo de reloj (oscilador) que va a usar (interno, externo, por cristal, etc.) Nosotros usaremos el RC interno porque no necesitamos una precisión extrema y no inutilizamos dos de las patas de E/S del PIC. b) El timer denominado ”perro guardián” lo usaremos deshabilitado c) El timer de encendido también deshabilitado d) Habilitaremos el borrado total en un reset. e) No interesa el siguiente que es la detección de apagado f) La siguiente opción debe habilitarse para poder luego programar el PIC con tensiones normales (de lo contrario hace falta mayor tensión para programarlo) g) Deshabilitamos la protección de lectura de la EEPROM h) Deshabilitamos la protección del código del programa Lo que sigue es quizás un poco engorroso, pero no se me ocurrió hasta ahora una mejor forma para hacerlo. Conceptualmente se trata de agregarle el archivo de texto donde estará definido nuestro programa; el problema es que el editor de textos es accesible sólo desde la aplicación y no he encontrado la manera de que agregue este archivo a la carpeta “archivos fuente” (source files) del proyecto y debe estar allí antes de ordenar la compilación porque el compilador busca en esa ruta el archivo de texto cuya extensión es .ASM. De modo que a seguir los pasos: 1) Llamar al editor desde el menú Archivos (File), nuevo (New). Aparecerá una ventana con el nombre “untitled” (sin título). 2) Hay que darle uno o dos TABs y pondremos cualquier cosa, por ejemplo la directiva para que use el encabezado que incluimos antes: #include<p16f628.inc> 59 3) Cerrar el editor, guardándolo con un nombre (mejor el del proyecto) y la extensión .ASM. (muy importante) 4) Cerrar el proyecto confirmando que se quiere guardar todo el espacio de trabajo, para que cuando lo abramos aparezcan las ventanas que están ahora. 5) Abrir el proyecto, ir a la carpeta “source files”, apretar botón derecho del mouse y seleccionar “add files” (agregar archivo), buscar el archivo recién guardado y abrirlo. Deberá aparecer dentro de la carpeta seleccionada. 6) Hacer doble click en el archivo para abrirlo. Fin del proceso de agregar archivo fuente. Se abrirá el editor junto con el archivo guardado, deberíamos ver que ahora tiene los números de línea y ha cambiado el color del texto. Estos colores indicarán si el precompilador “entiende” la sintaxis de la instrucción que estamos poniendo. Hay que tener en cuenta que la primer columna de la izquierda está reservada para etiquetas (labels), la segunda columna es para las instrucciones, la tercera es para los argumentos que necesitan las instrucciones y, eventualmente, la cuarta es para los comentarios; éstos deben estar separados por el carácter “;”. Los comandos del pre-procesador (include, radix, org, equ, list, start/end) no deben estar en la primera columna. 60 Escribir el programa como en un editor de texto común usando TAB o SPACE para espaciar y ENTER para cambiar de línea. Agregar END al final para avisarle al pre-procesador que ahí termina la compilación. COMPILACIÓN: Finalizada la edición del programa hay que compilarlo haciendo click aquí Aparecerá una ventana de diálogo preguntando si quieren direcciones relativas o absolutas, responder absolutas. En la ventana “salida” (output) se verá el progreso de la compilación, figurando en primer lugar las advertencias (warning) y los errores. Hay que corregir los errores de sintaxis hasta que sólo aparezcan advertencias y la leyenda de “compilación exitosa” (building succeeded). Las advertencias son generalmente porque faltaron argumentos en las instrucciones y el compilador tomó los valores que tiene por defecto o porque hay registros que están ubicados en bancos de memoria diferentes al cero y les avisa que ese registro está en el banco xx. Si el programa funciona, quiere decir que las advertencias no tienen importancia. Si el programa no funciona, comenzar a depurarlo verificando las advertencias que hizo el compilador. Si cambian algo del programa hay que compilarlo nuevamente. Para correr el programa hay que hacer click acá Si el programa no finaliza nunca, para pararlo hacer click acá Para resetear el programa se usa este botón (debe estar previamente parado) Para correr instrucción por instrucción Para agregar puntos de parada 61 Depurar un programa sin ver el valor de los registros sería imposible, es por eso que el MPLab tiene una cantidad de herramientas para poder echar un vistazo a lo que ocurre dentro del PIC a medida que transcurre el programa. Veremos sólo las que podrían usar más: a) Menú Ver, opción Watch: b) Menú Ver, opción Hardware stack c) Menú Ver, opción EEPROM d) Menú Debugger, opción StopWatch Watch: Abierta la ventana, conviene configurarla haciendo click con el botón derecho del mouse, elegir Properties, solapa Preferences y tildar la opción “Expand SFR registers”, esto permite ver cada uno de los bits (flags) que componen los registros del PIC; por ejemplo el registro de estado de la ALU tiene los flags que indican un desbordamiento (C), si la operación dio cero (Z) o el banco de memoria en uso. Para no tener que decodificar el estado de cada uno de ellos desde el valor hexadecimal del registro, con esta opción se despliegan dentro del registro que queramos cada uno de esos flags y el valor (0 o 1). Una vez compilado, en la opción “add symbol” aparecen las variables que hayamos usado en RAM para el diseño de nuestro programa. La opción “Add SFR” contiene los registros especiales como el de estado, el de trabajo y los puertos, entre muchos otros. Esta es la ventana indispensable para la depuración. Hardware stack Si tenemos problemas de lógica, habiendo usado varias llamadas a subrutinas, puede ser que hayamos olvidado algún RETURN y se está llenando la “pila”. En esta ventana podemos ver cada una de las llamadas y si regresa o no de cada una de ellas. EEPROM Nos muestra el contenido de la memoria permanente de lectura/escritura y permite, además, escribir en ella. Los valores que escribamos aquí serán puestos en el PIC cuando grabemos el programa en él. StopWatch Esta ventana nos muestra cuántos ciclos de instrucción (o tiempo) tardó nuestro programa hasta que paró, sea esto porque terminó o por una parada programada por nosotros con el símbolo “B” rojo que vimos antes. 62 Existen otras herramientas de software para simular el uso de los PIC, algunas más visuales, pero como esta es gratuita y está hecha por el fabricante del PIC, me merece más confianza que las otras. Ahora resta que usen este software para acostumbrarse a las posibilidades que tiene y que practiquen porque es una de las bases de la programación. 63