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.