Download Capítulo XII: EL HARDWARE DE APOYO AL
Document related concepts
no text concepts found
Transcript
Capítulo XII: EL HARDWARE DE APOYO AL MICROPROCESADOR En este capítulo se mostrará detenidamente el funcionamiento de todos los chips importantes que lleva el ordenador en la placa base y alguno de los colocados en las tarjetas de expansión. Nota: Por limitaciones técnicas, al describir los circuitos integrados las señales que son activas a nivel bajo no tendrán la tradicional barra negadora encima; en su lugar aparecerán precedidas del signo menos: -CS, -WR, -MEMR, ... En algunos casos, acceder directamente a los chips no es necesario: en general, es mejor dejar el trabajo al DOS, o en su defecto a la BIOS. Sin embargo, hay casos en que es estrictamente necesario hacerlo: por ejemplo, para programar temporizaciones, hacer sonidos, comunicaciones serie por interrupciones, acceso a discos de formato no estándar, etc. Algunas veces bastará con la información que aparece en el apartado donde se describe la relación del chip con los PC; sin embargo, a menudo será necesario consultar la información técnica del apartado ubicado inmediatamente antes, para lo que bastan unos conocimientos razonables de los sistemas digitales. Los ordenadores modernos normalmente no llevan los integrados explicados en este capítulo; sin embargo, poseen circuitos equivalentes que los emulan por completo. 12.1. - LAS CONEXIONES DEL 8088. Resulta interesante tener una idea global de las conexiones del 8086 con el exterior de cara a entender mejor la manera en que interacciona con el resto de los elementos del ordenador. Se ha elegido el 8088 por ser el primer procesador que tuvo el PC; a efectos de entender el resto del capítulo es suficiente con el 8088. El 8088 puede trabajar en dos modos: mínimo (pequeñas aplicaciones) y máximo (sistemas multiprocesador). Los requerimientos de conexión con el exterior cambian en función del modo que se decida emplear, aunque una parte de las señales es común en ambos. LÍNEAS COMUNES AL MODO MÁXIMO Y MÍNIMO DEL 8088. AD7..0: A15..8: Address Data Bus. Son líneas multiplexadas, que pueden actuar como bus de datos o de direcciones, evidentemente en tiempos distintos. Address Bus. En todo momento almacenan la parte media del bus de direcciones. Address/Status. Parte alta del bus de direcciones, multiplexada: cuando no salen direcciones, la línea S5 indica el estado del banderín de interrupciones; las líneas S4:S3 informan del A19..16/S6..3: registro de segmento empleado para realizar el acceso a memoria: 00-ES, 01-SS, 10-CS, 11DS; S6 no se usa. -RD: Read. Indica una lectura de memoria o de un dispositivo de entrada/salida. READY: Ready. Línea de entrada que indica el final de la operación de memoria o E/S. Interrupt Request. Línea de petición de interrupciones enmascarables; el 8088 la observa INTR: periódicamente. Test. En respuesta a la instrucción máquina WAIT (¡no TEST!), el 8088 se para a comprobar -TEST: esta línea hasta que se ponga a 0. Non-maskable Interrupt. Línea de petición de la interrupción de tipo 2, que no puede ser NMI: enmascarada. RESET: Provoca una inicialización interna que culmina saltando a FFFF:0. MN/-MX: Esta línea indica si se trata de un sistema mínimo o máximo. LÍNEAS EXCLUSIVAS DEL MODO MÍNIMO DEL 8088. IO/-M: -wr: -INTA: ALE: DT/-R: -DEN: HOLD: HLDA: -SS0: Status Line. Indica si se trata de un acceso a memoria o a un puerto de entrada/salida. No es válida todo el tiempo (solo a ratos). Write. Indica una escritura en memoria o en un dispositivo de entrada/salida (según el estado de IO/M). Interrupt Acknowledge. Es la señal de reconocimiento de interrupción (solicitada a través de INTR o NMI). Address Latch Enable. Indica al exterior que las líneas de dirección contienen una dirección válida, con objeto de que la circuitería externa la almacene en una pequeña memoria (latch). Señal necesaria sólo por culpa de la multiplexación. Data Transmit/Receive. Señal necesaria para emplear un transceiver 8286/8287 en el bus, con objeto de controlar el flujo de datos a través del mismo (si se recibe/transmite). Data Enable. Necesario también para emplear el transceiver: sirve como entrada de habilitación para el mismo. Hold. Línea de entrada para solicitar al 8088 que se desconecte de los buses. Empleada por los controladores de DMA. Hold Acknowledge. Línea complementaria de HOLD: el 8088 envía una señal de reconocimiento cuando se desconecta del bus. Status Line. Línea de apoyo que, junto con IO/-M y DT/-R, permite determinar con precisión el estado del bus: IO/-M DT/-R -SS0 Estado del bus ------- ------- ------- -----------------------------1 0 0 Reconocimiento de interrupción 1 0 1 Lectura de puerto E/S 1 1 0 Escritura en puerto E/S 1 1 1 Estado Halt 0 0 0 Acceso a código 0 0 1 Lectura de memoria 0 1 0 Escritura en memoria 0 1 1 Inactivo LÍNEAS EXCLUSIVAS DEL MODO MÁXIMO DEL 8088. -S0/-S1/-S2: Status. Estas líneas indican el estado del bus: -S2 -S1 -S0 Estado del bus ------- ------- ------- -----------------------------0 0 0 Reconocimiento de interrupción 0 0 1 Lectura de puerto E/S 0 1 0 Escritura en puerto E/S 0 1 1 Estado Halt 1 0 0 Acceso a código 1 0 1 Lectura de memoria 1 1 0 Escritura en memoria 1 1 1 Inactivo -RQ/Request/Grant. Estas patillas bidireccionales permiten a los demás procesadores conectados al bus GT0..1: forzar al 8088 a que libere el bus al final del ciclo en curso. Lock. Línea que sirve al 8088 para prohibir el acceso al bus a otros procesadores (se activa tras la instrucción máquina LOCK y dura mientras se ejecuta la siguiente instrucción -la que sigue a -LOCK: LOCK, que es realmente un prefijo-). También se activa automáticamente en los momentos críticos de un ciclo de interrupción. QS1/QS0: Queue Status. Permite determinar el estado de la cola de instrucciones del 8088. DIFERENCIAS IMPORTANTES CON EL 8086. El 8086 cambia el patillaje sensiblemente, aunque la mayoría de las señales son similares. En lugar de 8 líneas de datos y direcciones multiplexadas (AD0..7) el 8086 posee 16, ya que el bus de datos es de 16 bits. Existe una línea especialmente importante en el 8086, -BHE/S7 (Bus High Enables/Status), que normalmente indica si se accede a la parte alta del bus de datos o no (operaciones 8/16 bits). El 8086 posee una cola de instrucciones de 6 bytes, en lugar de 4. FORMATO DE LAS INSTRUCCIONES DEL 8086. Resulta absurdo estudiar la composición binaria de las instrucciones máquina de ningún procesador; en los casos en que sea necesario se pueden ver los códigos con alguna utilidad de depuración. Sin embargo, a título de curiosidad, se expone a continuación el formato general de las instrucciones (aunque hay algunas excepciones y casos especiales). +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ +--------------------+ +---------------------+ | Código de Operación | D | W | | MOD | REG | REG/MEM | | byte/palabra despl. | | byte/palabra inmed. | +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ +--------------------+ +---------------------+ El código de operación ocupa 6 bits; el bit D indica si es el operando fuente (=0) el que está en el campo registro (REG) o si lo es el operando destino (=1): la razón es que el 8086 sólo admite un operando a memoria, como mucho (o el fuente, o el destino, no los dos a la vez). El bit W indica el tamaño de la operación (byte/palabra). MOD indica el modo de direccionamiento: 00-sin desplazamiento (no existe campo de desplazamiento), 01desplazamiento de 8 bits, 10-desplazamiento de 16 bits y 11-registro (tanto fuente como destino están en registro). El campo REG indica el registro involucrado en la instrucción, que puede ser de 8 ó 16 bits (según indique W): 0-AX/AL, 1-CX/CL, 2-DX/DL, 3-BX/BL, 4-SP/AH, 5-BP/CH, 6-SI/DH, 7-DI/BH; en el caso de registros de segmento sólo son significativos los dos bits de menor peso: 00-ES, 01-CS, 10-SS, 11-DS. El campo R/M, en el caso de modo registro (MOD=11) se codifica igual que el campo REG; en caso contrario se indica la forma en que se direcciona la memoria: 0: [BX+SI+desp], 1: [BX+DI+desp], 2: [BP+SI+desp], 3: [BP+DI+desp], 4: [SI+desp], 5: [DI+desp], 6: [BP+desp], 7: [BX+desp]. Capítulo III: Microprocesadores 8086/88, 286, 386, 486 y Pentium. 3.1. - CARACTERÍSTICAS GENERALES. Los microprocesadores Intel 8086 y 8088 se desarrollan a partir de un procesador anterior, el 8080, que, en sus diversas encarnaciones -incluyendo el Zilog Z-80- ha sido la CPU de 8 bits de mayor éxito. Poseen una arquitectura interna de 16 bits y pueden trabajar con operandos de 8 y 16 bits; una capacidad de direccionamiento de 20 bits (hasta 1 Mb) y comparten el mismo juego de instrucciones. La filosofía de diseño de la familia del 8086 se basa en la compatibilidad y la creación de sistemas informáticos integrados, por lo que disponen de diversos coprocesadores como el 8089 de E/S y el 8087, coprocesador matemático de coma flotante. De acuerdo a esta filosofía y para permitir la compatibilidad con los anteriores sistemas de 8 bits, el 8088 se diseñó con un bus de datos de 8 bits, lo cual le hace más lento que su hermano el 8086, pues éste es capaz de cargar una palabra ubicada en una dirección par en un solo ciclo de memoria mientras el 8088 debe realizar dos ciclos leyendo cada vez un byte. Disponen de 92 tipos de instrucciones, que pueden ejecutar con hasta 7 modos de direccionamiento. Tienen una capacidad de direccionamiento en puertos de entrada y salida de hasta 64K (65536 puertos), por lo que las máquinas construidas entorno a estos microprocesadores no suelen emplear la entrada/salida por mapa de memoria, como veremos. Entre esas instrucciones, las más rápidas se ejecutan en 2 ciclos teóricos de reloj y unos 9 reales (se trata del movimiento de datos entre registros internos) y las más lentas en 206 (división entera con signo del acumulador por una palabra extraída de la memoria). Las frecuencias internas de reloj típicas son 4.77 MHz en la versión 8086; 8 MHz en la versión 8086-2 y 10 MHz en la 8086-1. Recuérdese que un MHz son un millón de ciclos de reloj, por lo que un PC estándar a 4,77 MHz puede ejecutar de 20.000 a unos 0,5 millones de instrucciones por segundo, según la complejidad de las mismas (un 486 a 50 MHz, incluso sin memoria caché externa es capaz de ejecutar entre 1,8 y 30 millones de estas instrucciones por segundo). El microprocesador Intel 80286 se caracteriza por poseer dos modos de funcionamiento completamente diferenciados: el modo real en el que se encuentra nada más ser conectado a la corriente y el modo protegido en el que adquiere capacidad de proceso multitarea y almacenamiento en memoria virtual. El proceso multitarea consiste en realizar varios procesos de manera aparentemente simultánea, con la ayuda del sistema operativo para conmutar automáticamente de uno a otro optimizando el uso de la CPU, ya que mientras un proceso está esperando a que un periférico complete una operación, se puede atender otro proceso diferente. La memoria virtual permite al ordenador usar más memoria de la que realmente tiene, almacenando parte de ella en disco: de esta manera, los programas creen tener a su disposición más memoria de la que realmente existe; cuando acceden a una parte de la memoria lógica que no existe físicamente, se produce una interrupción y el sistema operativo se encarga de acceder al disco y traerla. Cuando la CPU está en modo protegido, los programas de usuario tienen un acceso limitado al juego de instrucciones; sólo el proceso supervisor -normalmente el sistema operativo- está capacitado para realizar ciertas tareas. Esto es así para evitar que los programas de usuario puedan campar a sus anchas y entrar en conflictos unos con otros, en materia de recursos como memoria o periféricos. Además, de esta manera, aunque un error software provoque el cuelgue de un proceso, los demás pueden seguir funcionando normalmente, y el sistema operativo podría abortar el proceso colgado. Por desgracia, con el DOS el 286 no está en modo protegido y el cuelgue de un solo proceso -bien el programa principal o una rutina operada por interrupciones- significa la caída inmediata de todo el sistema. El 8086 no posee ningún mecanismo para apoyar la multitarea ni la memoria virtual desde el procesador, por lo que es difícil diseñar un sistema multitarea para el mismo y casi imposible conseguir que sea realmente operativo. Obviamente, el 286 en modo protegido pierde absolutamente toda la compatibilidad con los procesadores anteriores. Por ello, en este libro sólo trataremos el modo real, único disponible bajo DOS, aunque veremos alguna instrucción extra que también se puede emplear en modo real. Las características generales del 286 son: tiene un bus de datos de 16 bits, un bus de direcciones de 24 bits (16 Mb); posee 25 instrucciones más que el 8086 y admite 8 modos de direccionamiento. En modo virtual permite direccionar hasta 1 Gigabyte. Las frecuencias de trabajo típicas son de 12 y 16 MHz, aunque existen versiones a 20 y 25 MHz. Aquí, la instrucción más lenta es la misma que en el caso del 8086, solo que emplea 29 ciclos de reloj en lugar de 206. Un 286 de categoría media (16 MHz) podría ejecutar más de medio millón de instrucciones de estas en un segundo, casi 15 veces más que un 8086 medio a 8 MHz. Sin embargo, transfiriendo datos entre registros la diferencia de un procesador a otro se reduce notablemente, aunque el 286 es más rápido y no sólo gracias a los MHz adicionales. Versiones mejoradas de los Intel 8086 y 8088 se encuentran también en los procesadores NEC-V30 y NEC-V20 respectivamente. Ambos son compatibles Hardware y Software, con la ventaja de que el procesado de instrucciones está optimizado, llegando a superar casi en tres veces la velocidad de los originales en algunas instrucciones aritméticas. También poseen una cola de prebúsqueda mayor (cuando el microprocesador está ejecutando una instrucción, si no hace uso de los buses externos, carga en una cola FIFO de unos pocos bytes las posiciones posteriores a la que está procesando, de esta forma una vez que concluye la instrucción en curso ya tiene internamente la que le sigue). Además, los NEC V20 y V30 disponen de las mismas instrucciones adicionales del 286 en modo real, al igual que el 80186 y el 80188. Por su parte, el 386 dispone de una arquitectura de registros de 32 bits, con un bus de direcciones también de 32 bits (direcciona hasta 4 Gigabytes = 4096 Mb) y más modos posibles de funcionamiento: el modo real (compatible 8086), el modo protegido (relativamente compatible con el del 286), un modo protegido propio que permite -¡por fin!- romper la barrera de los tradicionales segmentos y el modo «virtual 86», en el que puede emular el funcionamiento simultáneo de varios 8086. Una vez más, todos los modos son incompatibles entre sí y requieren de un sistema operativo específico: si se puede perdonar al fabricante la pérdida de compatibilidad del modo avanzados del 286 frente al 8086, debido a la lógica evolución tecnológica, no se puede decir lo mismo del 386 respecto al 286: no hubiera sido necesario añadir un nuevo modo protegido si hubiera sido mejor construido el del 286 apenas un par de años atrás. Normalmente, los 386 suelen operar en modo real (debido al DOS) por lo que no se aprovechan las posibilidades multitarea ni de gestión de memoria. Por otra parte, aunque se pueden emplear los registros de 32 bits en modo real, ello no suele hacerse -para mantener la compatibilidad con procesadores anteriores- con lo que de entrada se está tirando a la basura un 50% de la capacidad de proceso del chip, aunque por fortuna estos procesadores suelen trabajar a frecuencias de 16/20 MHz (obsoletas) y normalmente de 33 y hasta 40 MHz. El 386sx es una variante del 386 a nivel de hardware, aunque es compatible en software. Básicamente, es un 386 con un bus de datos de sólo 16 bits -más lento, al tener que dar dos pasadas para un dato de 32 bits-. De hecho, podría haber sido diseñado perfectamente para mantener una compatibilidad hardware con el 286, aunque el fabricante lo evitó probablemente por razones comerciales. El 486 se diferencia del 386 en la integración en un solo chip del coprocesador 387. También se ha mejorado la velocidad de operación: la versión de 25 MHz dobla en términos reales a un 386 a 25 MHz equipado con el mismo tamaño de memoria caché. La versión 486sx no se diferencia en el tamaño del bus, también de 32 bits, sino en la ausencia del 387 (que puede ser añadido externamente). También existen versiones de 486 con buses de 16 bits, el primer fabricante de estos chips, denominados 486SLC, ha sido Cyrix. Una tendencia iniciada por el 486 fue la de duplicar la velocidad del reloj interno (pongamos por caso de 33 a 66 MHz) aunque en las comunicaciones con los buses exteriores se respeten los 33 MHz. Ello agiliza la ejecución de las instrucciones más largas: bajo DOS, el rendimiento general del sistema se puede considerar prácticamente el doble. Son los chips DX2 (también hay una variante a 50 MHz: 25 x 2). La culminación de esta tecnología viene de la mano de los DX4 a 75/100 MHz (25/33 x 3). El Pentium, último procesador de Intel en el momento de escribirse estas líneas, se diferencia respecto al 486 en el bus de datos (ahora de 64 bits, lo que agiliza los accesos a memoria) y en un elevadísimo nivel de optimización y segmentación que le permite, empleando compiladores optimizados, simultanear en muchos casos la ejecución de dos instrucciones consecutivas. Posee dos cachés internas, tiene capacidad para predecir el destino de los saltos y la unidad de coma flotante experimenta elevadas mejoras. Sin embargo, bajo DOS, un Pentium básico sólo es unas 2 veces más rápido que un 486 a la misma frecuencia de reloj. Comenzó en 60/90 MHz hasta los 166/200/233 MHz de las últimas versiones (Pentium Pro y MMX), que junto a diversos clones de otros fabricantes, mejoran aún más el rendimiento. Todos los equipos Pentium emplean las técnicas DX, ya que las placas base típicas corren a 60 MHz. Para hacerse una idea, por unas 200000 pts de 1997 un equipo Pentium MMX a 233 MHz es cerca de 2000 veces más rápido en aritmética entera que el IBM PC original de inicios de la década de los 80; en coma flotante la diferencia aumenta incluso algunos órdenes más de magnitud. Y a una fracción del coste (un millón de pts de aquel entonces que equivale a unos 2,5 millones de hoy en día). Aunque no hay que olvidar la revolución del resto de los componentes: 100 veces más memoria (central y de vídeo), 200 veces más grande el disco duro... y que un disco duro moderno transfiere datos 10 veces más deprisa que la memoria de aquel IBM PC original. Por desgracia, el software no ha mejorado el rendimiento, ni remotamente, en esa proporción: es la factura pasada por las técnicas de programación cada vez a un nivel más alto (aunque nadie discute sus ventajas). Una característica de los microprocesadores a partir del 386 es la disponibilidad de memorias caché de alta velocidad de acceso -muy pocos nanosegundos- que almacenan una pequeña porción de la memoria principal. Cuando la CPU accede a una posición de memoria, cierta circuitería de control se encarga de ir depositando el contenido de esa posición y el de las posiciones inmediatamente consecutivas en la memoria caché. Cuando sea necesario acceder a la instrucción siguiente del programa, ésta ya se encuentra en la caché y el acceso es muy rápido. Lo ideal sería que toda la memoria del equipo fuera caché, pero esto no es todavía posible actualmente. Una caché de tamaño razonable puede doblar la velocidad efectiva de proceso de la CPU. El 8088 carecía de memoria caché, pero sí estaba equipado con una unidad de lectura adelantada de instrucciones con una cola de prebúsqueda de 4 bytes: de esta manera, se agilizaba ya un tanto la velocidad de proceso al poder ejecutar una instrucción al mismo tiempo que iba leyendo la siguiente. 3.2. - REGISTROS DEL 8086 Y DEL 286. Estos procesadores disponen de 14 registros de 16 bits (el 286 alguno más, pero no se suele emplear bajo DOS). La misión de estos registros es almacenar las posiciones de memoria que van a experimentar repetidas manipulaciones, ya que los accesos a memoria son mucho más lentos que los accesos a los registros. Además, hay ciertas operaciones que sólo se pueden realizar sobre los registros. No todos los registros sirven para almacenar datos, algunos están especializados en apuntar a las direcciones de memoria. La mecánica básica de funcionamiento de un programa consiste en cargar los registros con datos de la memoria o de un puerto de E/S, procesar los datos y devolver el resultado a la memoria o a otro puerto de E/S. Obviamente, si un dato sólo va a experimentar un cambio, es preferible realizar la operación directamente sobre la memoria, si ello es posible. A continuación se describen los registros del 8086. AX SP CS IP BX BP DS flags CX SI SS DX DI ES Registros Registro Registros Registros punteros de puntero de de de datos pila e instrucciones y segmento índices flags - Registros de datos: AX, BX, CX, DX: pueden utilizarse bien como registros de 16 bits o como dos registros separados de 8 bits (byte superior e inferior) cambiando la X por H o L según queramos referirnos a la parte alta o baja respectivamente. Por ejemplo, AX se descompone en AH (parte alta) y AL (parte baja). Evidentemente, ¡cualquier cambio sobre AH o AL altera AX!: valga como ejemplo que al incrementar AH se le están añadiendo 256 unidades a AX. AX = Acumulador. Es el registro principal, es utilizado en las instrucciones de multiplicación y división y en algunas instrucciones aritméticas especializadas, así como en ciertas operaciones de carácter específico como entrada, salida y traducción. Obsérvese que el 8086 es suficientemente potente para realizar las operaciones lógicas, la suma y la resta sobre cualquier registro de datos, no necesariamente el acumulador. BX = Base. Se usa como registro base para referenciar direcciones de memoria con direccionamiento indirecto, manteniendo la dirección de la base o comienzo de tablas o matrices. De esta manera, no es preciso indicar una posición de memoria fija, sino la número BX (así, haciendo avanzar de unidad en unidad a BX, por ejemplo, se puede ir accediendo a un gran bloque de memoria en un bucle). CX = Contador. Se utiliza comúnmente como contador en bucles y operaciones repetitivas de manejo de cadenas. En las instrucciones de desplazamiento y rotación se utiliza como contador de 8 bits. DX = Datos. Usado en conjunción con AX en las operaciones de multiplicación y división que involucran o generan datos de 32 bits. En las de entrada y salida se emplea para especificar la dirección del puerto E/S. - Registros de segmento: Definen áreas de 64 Kb dentro del espacio de direcciones de 1 Mb del 8086. Estas áreas pueden solaparse total o parcialmente. No es posible acceder a una posición de memoria no definida por algún segmento: si es preciso, habrá de moverse alguno. CS = Registro de segmento de código (code segment). Contiene la dirección del segmento con las instrucciones del programa. Los programas de más de 64 Kb requieren cambiar CS periódicamente. DS = Registro de segmento de datos (data segment). Segmento del área de datos del programa. SS = Registro de segmento de pila (stack segment). Segmento de pila. ES = Registro de segmento extra (extra segment). Segmento de ampliación para zona de datos. Es extraordinariamente útil actuando en conjunción con DS: con ambos se puede definir dos zonas de 64 Kb, tan alejadas como se desee en el espacio de direcciones, entre las que se pueden intercambiar datos. - Registros punteros de pila: SP = Puntero de pila (stack pointer). Apunta a la cabeza de la pila. Utilizado en las instrucciones de manejo de la pila. BP = Puntero base (base pointer). Es un puntero de base, que apunta a una zona dentro de la pila dedicada al almacenamiento de datos (variables locales y parámetros de las funciones en los programas compilados). - Registros índices: SI = Índice fuente (source index). Utilizado como registro de índice en ciertos modos de direccionamiento indirecto, también se emplea para guardar un valor de desplazamiento en operaciones de cadenas. DI = Índice destino (destination index). Se usa en determinados modos de direccionamiento indirecto y para almacenar un desplazamiento en operaciones con cadenas. - Puntero de instrucciones o contador de programa: IP = Puntero de instrucción (instruction pointer). Marca el desplazamiento de la instrucción en curso dentro del segmento de código. Es automáticamente modificado con la lectura de una instrucción. - Registro de estado o de indicadores (flags). Es un registro de 16 bits de los cuales 9 son utilizados para indicar diversas situaciones durante la ejecución de un programa. Los bits 0, 2, 4, 6, 7 y 11 son indicadores de condición, que reflejan los resultados de operaciones del programa; los bits del 8 al 10 son indicadores de control y el resto no se utilizan. Estos indicadores pueden ser comprobados por las instrucciones de salto condicional, lo que permite variar el flujo secuencial del programa según el resultado de las operaciones. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 OF DF IF TF SF ZF AF PF CF CF (Carry Flag): Indicador de acarreo. Su valor más habitual es lo que nos llevamos en una suma o resta. OF (Overflow Flag): Indicador de desbordamiento. Indica que el resultado de una operación no cabe en el tamaño del operando destino. ZF (Zero Flag): Indicador de resultado 0 o comparación igual. SF (Sign Flag): Indicador de resultado o comparación negativa. PF (Parity Flag): Indicador de paridad. Se activa tras algunas operaciones aritmético-lógicas para indicar que el número de bits a uno resultante es par. AF (Auxiliary Flag): Para ajuste en operaciones BCD. DF (Direction Flag): Indicador de dirección. Manipulando bloques de memoria, indica el sentido de avance (ascendente/descendente). IF (Interrupt Flag): Indicador de interrupciones: puesto a 1 están permitidas. TF (Trap Flag): Indicador de atrape (ejecución paso a paso). 3.3. - REGISTROS DEL 386 Y PROCESADORES SUPERIORES. Los 386 y superiores disponen de muchos más registros de los que vamos a ver ahora. Sin embargo, bajo el sistema operativo DOS sólo se suelen emplear los que veremos, que constituyen básicamente una extensión a 32 bits de los registros originales del 8086. Se amplía el tamaño de los registros de datos (que pueden ser accedidos en fragmentos de 8, 16 ó 32 bits) y se añaden dos nuevos registros de segmento multipropósito (FS y GS). Algunos de los registros aquí mostrados son realmente de 32 bits (como EIP en vez de IP), pero bajo sistema operativo DOS no pueden ser empleados de manera directa, por lo que no les consideraremos. 3.4. - MODOS DE DIRECCIONAMIENTO. Son los distintos modos de acceder a los datos en memoria por parte del procesador. Antes de ver los modos de direccionamiento, echaremos un vistazo a la sintaxis general de las instrucciones, ya que pondremos alguna en los ejemplos: INSTRUCCIÓN DESTINO, FUENTE Donde destino indica dónde se deja el resultado de la operación en la que pueden participar (según casos) FUENTE e incluso el propio DESTINO. Hay instrucciones, sin embargo, que sólo tienen un operando, como la siguiente, e incluso ninguno: INSTRUCCIÓN DESTINO Como ejemplos, aunque no hemos visto aún las instrucciones utilizaremos un par de ellas: la de copia o movimiento de datos (MOV) y la de suma (ADD). 3.4.1. - ORGANIZACIÓN DE DIRECCIONES: SEGMENTACIÓN. Como ya sabemos, los microprocesadores 8086 y compatibles poseen registros de un tamaño máximo de 16 bits que direccionarían hasta 64K; en cambio, la dirección se compone de 20 bits con capacidad para 1Mb, hay por tanto que recurrir a algún artificio para direccionar toda la memoria. Dicho artificio consiste en la segmentación: se trata de dividir la memoria en grupos de 64K. Cada grupo se asocia con un registro de segmento; el desplazamiento (offset) dentro de ese segmento lo proporciona otro registro de 16 bits. La dirección absoluta se calcula multiplicando por 16 el valor del registro de segmento y sumando el offset, obteniéndose una dirección efectiva de 20 bits. Esto equivale a concebir el mecanismo de generación de la dirección absoluta, como si se tratase de que los registros de segmento tuvieran 4 bits a 0 (imaginarios) a la derecha antes de sumarles el desplazamiento: dirección = segmento * 16 + offset En la práctica, una dirección se indica con la notación SEGMENTO:OFFSET; además, una misma dirección puede expresarse de más de una manera: por ejemplo, 3D00h:0300h es equivalente a 3D30:0000h. Es importante resaltar que no se puede acceder a más de 64 Kb en un segmento de datos. Por ello, en los procesadores 386 y superiores no se deben emplear registros de 32 bit para generar direcciones (bajo DOS), aunque para los cálculos pueden ser interesantes (no obstante, sí sería posible configurar estos procesadores para poder direccionar más memoria bajo DOS con los registros de 32 bits, aunque no resulta por lo general práctico). 3.4.2. - MODOS DE DIRECCIONAMIENTO. - Direccionamiento inmediato: El operando es una constante situada detrás del código de la instrucción. Sin embargo, como registro destino no se puede indicar uno de segmento (habrá que utilizar uno de datos como paso intermedio). ADD AX,0fffh El número hexadecimal 0fffh es la constante numérica que en el direccionamiento inmediato se le sumará al registro AX. Al trabajar con ensambladores, se pueden definir símbolos constantes (ojo, no variables) y es más intuitivo: dato EQU MOV 0fffh AX,dato ; símbolo constante Si se referencia a la dirección de memoria de una variable de la siguiente forma, también se trata de un caso de direccionamiento inmediato: dato DW MO 0fffh AX,OFFSET dato ; ahora es una variable ; AX = "dirección de memoria" de dato Porque hay que tener en cuenta que cuando traduzcamos a números el símbolo podría quedar: 17F3:0A11 DW MOV FFF AX,0A11 - Direccionamiento de registro: Los operandos, necesariamente de igual tamaño, están contenidos en los registros indicados en la instrucción: MOV MOV DX,AX AH,AL - Direccionamiento directo o absoluto: El operando está situado en la dirección indicada en la instrucción, relativa al segmento que se trate: MOV MOV AX,[57D1h] AX,ES:[429Ch] Esta sintaxis (quitando la 'h' de hexadecimal) sería la que admite el programa DEBUG (realmente habría que poner, en el segundo caso, ES: en una línea y el MOV en otra). Al trabajar con ensambladores, las variables en memoria se pueden referenciar con etiquetas simbólicas: dato MOV MOV AX,dato AX,ES:dato DW 1234h ; variable del programa En el primer ejemplo se transfiere a AX el valor contenido en la dirección apuntada por la etiqueta dato sobre el segmento de datos (DS) que se asume por defecto; en el segundo ejemplo se indica de forma explícita el segmento tratándose del segmento ES. La dirección efectiva se calcula de la forma ya vista con anterioridad: Registro de segmento * 16 + desplazamiento_de_dato (este desplazamiento depende de la posición al ensamblar el programa). - Direccionamiento indirecto: El operando se encuentra en una dirección señalada por un registro de segmento*16 más un registro base (BX/BP) o índice (SI/DI). (Nota: BP actúa por defecto con SS). MOV MOV AX,[BP] ES:[DI],AX ; AX = [SS*16+BP] ; [ES*16+DI] = AX - Indirecto con índice o indexado: El operando se encuentra en una dirección determinada por la suma de un registro de segmento*16, un registro de índice, SI o DI y un desplazamiento de 8 ó 16 bits. Ejemplos: MOV ADD AX,[DI+DESP] [SI+DESP],BX ó ó MOV ADD AX,desp[DI] desp[SI],BX - Indirecto con base e índice o indexado a base: El operando se encuentra en una dirección especificada por la suma de un registro de segmento*16, uno de base, uno de índice y opcionalmente un desplazamiento de 8 ó 16 bits: MOV MOV AX,ES:[BX+DI+DESP] CS:[BX+SI+DESP],CX ó ó MOV MOV AX,ES:desp[BX][DI] CS:desp[BX][SI],CX Combinaciones de registros de segmento y desplazamiento. Como se ve en los modos de direccionamiento, hay casos en los que se indica explícitamente el registro de segmento a usar para acceder a los datos. Existen unos segmentos asociados por defecto a los registros de desplazamiento (IP, SP, BP, BX, DI, SI); sólo es necesario declarar el segmento cuando no coincide con el asignado por defecto. En ese caso, el ensamblador genera un byte adicional (a modo de prefijo) para indicar cuál es el segmento referenciado. La siguiente tabla relaciona las posibles combinaciones de los registros de segmento y los de desplazamiento: CS SS DS ES IP Sí No No No SP No Sí No No BP con prefijo por defecto con prefijo con prefijo BX con prefijo con prefijo por defecto con prefijo SI con prefijo con prefijo por defecto con prefijo DI con prefijo con prefijo por defecto con prefijo(1) (1) También por defecto en el manejo de cadenas. Los 386 y superiores admiten otros modos de direccionamiento más sofisticados, que se verán en el próximo capítulo, después de conocer todas las instrucciones del 8086. Por ahora, con todos estos modos se puede considerar que hay más que suficiente. De hecho, algunos se utilizan en muy contadas ocasiones. 3.5. - LA PILA. La pila es un bloque de memoria de estructura LIFO (Last Input First Output: último en entrar, primero en salir) que se direcciona mediante desplazamientos desde el registro SS (segmento de pila). Las posiciones individuales dentro de la pila se calculan sumando al contenido del segmento de pila SS un desplazamiento contenido en el registro puntero de pila SP. Todos los datos que se almacenan en la pila son de longitud palabra, y cada vez que se introduce algo en ella por medio de las instrucciones de manejo de pila (PUSH y POP), el puntero se decrementa en dos; es decir, la pila avanza hacia direcciones decrecientes. El registro BP suele utilizarse normalmente para apuntar a una cierta posición de la pila y acceder indexadamente a sus elementos -generalmente en el caso de variablessin necesidad de desapilarlos para consultarlos. La pila es utilizada frecuentemente al principio de una subrutina para preservar los registros que no se desean modificar; al final de la subrutina basta con recuperarlos en orden inverso al que fueron depositados. En estas operaciones conviene tener cuidado, ya que la pila en los 8086 es común al procesador y al usuario, por lo que se almacenan en ella también las direcciones de retorno de las subrutinas. Esta última es, de hecho, la más importante de sus funciones. La estructura de pila permite que unas subrutinas llamen a otras que a su vez pueden llamar a otras y así sucesivamente: en la pila se almacenan las direcciones de retorno, que serán las de la siguiente instrucción que provocó la llamada a la subrutina. Así, al retornar de la subrutina se extrae de la pila la dirección a donde volver. Los compiladores de los lenguajes de alto nivel la emplean también para pasar los parámetros de los procedimientos y para generar en ella las variables automáticas variables locales que existen durante la ejecución del subprograma y se destruyen inmediatamente después-. Por ello, una norma básica es que se debe desapilar siempre todo lo apilado para evitar una pérdida de control inmediata del ordenador. Ejemplo de operación sobre la pila (todos los datos son arbitrarios): 3.6. - UN PROGRAMA DE EJEMPLO. Aunque las instrucciones del procesador no serán vistas hasta el próximo capítulo, con objeto de ayudar a la imaginación del lector elaboraremos un primer programa de ejemplo en lenguaje ensamblador. La utilidad de este programa es dejar patente que lo único que entiende el 8086 son números, aunque nosotros nos referiremos a ellos con unos símbolos que faciliten entenderlos. También es interesante este ejemplo para afianzar el concepto de registro de segmento. En este programa sólo vamos a emplear las instrucciones MOV, ya conocida, y alguna otra más como la instrucción INC (incrementar), DEC (disminuir una unidad) y JNZ (saltar si el resultado no es cero). Suponemos que el programa está ubicado a partir de la dirección de memoria 14D3:7A10 (arbitrariamente elegida) y que lo que pretendemos hacer con él es limpiar la pantalla. Como el ordenador es un PC con monitor en color, la pantalla de texto comienza en B800:0000 (no es más que una zona de memoria). Por cada carácter que hay en dicha pantalla, comenzando arriba a la izquierda, a partir de la dirección B800:0000 tenemos dos bytes: el primero, con el código ASCII del carácter y el segundo con el color. Lo que vamos a hacer es rellenar los 2000 caracteres (80 columnas x 25 líneas) con espacios en blanco (código ASCII 32, ó 20h en hexadecimal), sin modificar el color que hubiera antes. Esto es, se trata de poner el valor 32 en la dirección B800:0000, la B800:0002, la B800:0004... y así sucesivamente. El programa quedaría en memoria de esta manera: La primera columna indica la dirección de memoria donde está el programa que se ejecuta (CS=14D3h e IP=7A10h al principio). La segunda columna constituye el código máquina que interpreta el 8086. Algunas instrucciones ocupan un byte de memoria, otras dos ó tres (las hay de más). La tercera columna contiene el nombre de las instrucciones, algo mucho más legible para los humanos que los números: 14D3:7A10 B9 D0 07 MOV CX,7D0H = 7D0 hexadecimal) 14D3:7A13 B8 00 B8 MOV AX,0B800h de pantalla 14D3:7A16 8E D8 MOV DS,AX datos a la misma 14D3:7A18 BB 00 00 MOV BX,0 carácter ASCII de la pantalla 14D3:7A1B C6 07 20 MOV BYTE PTR [BX],32 indicar que 32 es de 8 bits 14D3:7A1E 43 INC BX byte de color 14D3:7A1F 43 INC BX siguiente carácter ASCII 14D3:7A20 49 DEC CX carácter menos 14D3:7A21 75 F8 JNZ -8 bytes atrás (a 14D3:7A1B) ; CX = 7D0h (2000 decimal ; segmento de la memoria ; apuntar segmento de ; apuntar al primer ; se pone BYTE PTR para ; BX=BX+1 -< apuntar al ; BX=BX+1 -< apuntar al ; CX=CX-1 -< queda un ; si CX no es 0, saltar 8 Como se puede ver, la segunda instrucción (bytes de código máquina 0B8h, 0 y 0B8h colocados en posiciones consecutivas) está colocada a partir del desplazamiento 7A13h, ya que la anterior que ocupaba 3 bytes comenzaba en 7A10h. En el ejemplo cargamos el valor 0B800h en DS apoyándonos en AX como intermediario. El motivo es que los registros de segmento no admiten el direccionamiento inmediato. A medida que se van haciendo programas, el ensamblador da mensajes de error cuando se encuentra con estos fallos y permite ir aprendiendo con facilidad las normas, que tampoco son demasiadas. La instrucción MOV BYTE PTR [BX],32 equivale a decir: «poner en la dirección de memoria apuntada por BX (DS:[BX] para ser más exactos) el byte de valor 32». El valor 0F8h del código máquina de la última instrucción es el complemento a dos (número negativo) del valor 8. Normalmente, casi nunca habrá que ensamblar a mano consultando unas tablas, como hemos hecho en este ejemplo. Sin embargo, la mejor manera de aprender ensamblador es no olvidando la estrecha relación de cada línea de programa con la CPU y la memoria.