|
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 de datos | Registros punteros de pila e índices | Registros de segmento | Registro puntero de instrucciones y flags |
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 |
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,0fffhEl 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 0fffh ; símbolo constante
MOV AX,dato
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 0fffh ; ahora es una variable
MO AX,OFFSET dato ; 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 FFF
MOV AX,0A11
- Direccionamiento de registro: Los operandos, necesariamente de igual tamaño, están contenidos en
los registros indicados en la instrucción:
MOV DX,AX
MOV 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 AX,[57D1h]
MOV 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:
MOV AX,dato
MOV AX,ES:dato
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 AX,[BP] ; AX = [SS*16+BP]
MOV ES:[DI],AX ; [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 AX,[DI+DESP] ó MOV AX,desp[DI]
ADD [SI+DESP],BX ó ADD 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 AX,ES:[BX+DI+DESP] ó MOV AX,ES:desp[BX][DI]
MOV CS:[BX+SI+DESP],CX ó MOV 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) |
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 variables- sin 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 ; CX = 7D0h (2000 decimal = 7D0 hexadecimal) 14D3:7A13 B8 00 B8 MOV AX,0B800h ; segmento de la memoria de pantalla 14D3:7A16 8E D8 MOV DS,AX ; apuntar segmento de datos a la misma 14D3:7A18 BB 00 00 MOV BX,0 ; apuntar al primer carácter ASCII de la pantalla 14D3:7A1B C6 07 20 MOV BYTE PTR [BX],32 ; se pone BYTE PTR para indicar que 32 es de 8 bits 14D3:7A1E 43 INC BX ; BX=BX+1 -< apuntar al byte de color 14D3:7A1F 43 INC BX ; BX=BX+1 -< apuntar al siguiente carácter ASCII 14D3:7A20 49 DEC CX ; CX=CX-1 -< queda un carácter menos 14D3:7A21 75 F8 JNZ -8 ; si CX no es 0, saltar 8 bytes atrás (a 14D3:7A1B)
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.