LECCION 1:

CONOCIENDO LA MAQUINA.

Hola a todos los seguidores del curso de ensamblador de AESOFT. Esta primera lecci¢n va a ser sobre todo teorica. No se puede programar en ensamblador sin tener un conocimiento mas o menos profundo acerca de las peculiaridades de la maquina que vamos a programar. Esto es obvio, ya que en ensamblador tenemos un control total del sistema, por tanto, cuanto mas sepamos acerca de ellas mas provecho podremos sacar del mismo, y mejores resultados obtendremos. En primer lugar debemos saber con que‚ estamos trabajando, y no me refiero al lenguaje en si, sino al ordenador en cuestion. Tendremos que conocer de que‚ partes consta, como se comunican entre si, etc. Esta parte teorica es casi imprescindible para poder entender ciertas tecnicas de programacion. Deciros tambien que vamos a estudiar el Pc en general, no distinguiremos entre distintas familias de procesadores (8086,286,386,etc) ya que vamos a utilizar ensamblador del 8086. En realidad es el ensamblador que yo utilizo. Por tanto, aunque estemos programando sobre un 80386 o un 80486, desarrollaremos codigo ejecutable para la familia 80x86 en general. Cualquier exposicion que haga sera valida para toda la familia Pc. Bueno... Despues de este preambulo, pasamos al meollo de la cuesti¢n: Un Pc es un ordenador o computador, como mas os guste, compuesto principalmente por procesador, chips de memoria, varios chips inteligentes o programables, y el bus de datos y direcciones. Junto con todo esto, nos encontramos los perifericos como son monitor, disqueteras, teclado, etc, que se comunican con el procesador. Esto (la comunicacion del procesador con perifericos) se vera mas adelante.

El procesador: Es el chip que ejecuta los programas. El procesador o CPU, lleva a cabo una gran variedad de calculos, comparaciones numericas y transferencias de datos como respuesta a las peticiones de los programas que estan siendo ejecutados en memoria. La CPU controla las operaciones basicas del ordenador enviando y recibiendo señales de control, direcciones de memoria y datos de un lugar a otro del ordenador a traves de un grupo de 'sendas electronicas' llamadas BUS. Localizadas a lo largo de este bus estan las puertas de entrada y salida (E/S en castellano o I/O en ingles), las cuales conectan a la memoria y a los chips de apoyo al bus. Los datos pasan a traves de estas puertas de E/S mientras viajan desde y hasta la CPU y otras partes del ordenador. Como os decia antes, trataremos el procesador 8086, siendo valido todo lo que digamos para el resto de procesadores de la familia PC. El procesador 8086 es un procesador de 16 bits. Esto quiere decir entre otras cosas, que el procesador va a manipular en una sola operacion datos de hasta 16 bits. Es decir, cuando transfiera datos a la memoria o los traiga desde ella, lo podra hacer de 16 bits en 16 bits. Aqui juega un papel decisivo el BUS de datos, ya que es por el por donde circulan los datos en las transferencias. Mas detalles unas cuantas lineas mas abajo.

El procesador cuenta con una serie de registros usados para realizar las operaciones de calculo, y como almacenamiento de datos.

Para que os hagais una idea, un registro del procesador es algo asi como una zona de memoria dentro del procesador donde se puede almacenar informacion, de forma que el acceso a esta informacion es instantaneo, ya que no hay que utilizar el bus de datos que conecta el procesador con la memoria para obtener dichos datos.

Estos registros se dividen en 5 grupos, segun sus funciones:

1.Registros de datos: AX, BX, CX y DX.

Se usan para calculo y almacenamiento de proposito general. Son utilizados por los programas para realizar calculos, asi como para transferir datos de una posicion de memoria a otra, ya que no se puede hacer de forma directa. 

Es decir, que no podemos transferir un dato de la posicion de memoria X a la posicion Y sin antes depositar ese dato temporalmente en un registro del procesador.

Estos registros tienen una longitud de 16 bits, pero podemos descomponerlos cuando nos interese en un par de registros de 8 bits.

Quedando de la forma siguiente:

AX = AH + AL

Siendo AX el registro de 16 bits, compuesto por la conjuncion (que no la suma) de el registro AH de 8 bits (los 8 bits mas signiicativos o de mas a la izquierda) y el registro AL de 8 bits (los 8 bits menos significativos o de mas a la derecha).

BX = BH + BL

CX = CH + CL

DX = DH + DL

Para estos tres registros se aplica lo mismo que para el registro AX.

Cada uno de estos registros tiene funciones especiales que es interesante conocer. Por ejemplo el registro AX es el llamado acumulador, hace que muchas operaciones tengan una forma mas corta, ya que lo especifican implicitamente. Es decir, que hay operaciones que actuan sobre el registro AX en particular.

BX se suele utilizar en muchas instrucciones como registro base, sobre todo en transeferencias de datos entre memoria y procesador. 

CX es el registro contador, muchas instrucciones lo utilizan para hacer incrementos o decrementos automaticos, para realizar bucles, etc.

DX es el registro de datos, se suele utilizar para operaciones de 32 bits, para almacenar los 16 bits (o palabra) mas significativos.

2.Registros Indice: SI, DI, BP y SP.

Se utilizan para acceder a memoria cuando se  establece el modo de direccionamiento mediante indexacion o con punteros.

SI y DI indican el indice fuente y destino respectivamente.

BP y SP indican el puntero base y el puntero de la pila respectivamenete.

Mas adelante hablaremos de la pila (que es un tipo de datos estructurado).

Estos 4 registros son de 16 bits, y no pueden ser utilizados como registros dobles de 8 bits.

3.Registros de Segmento: CS, DS, SS y ES.

Estos registros apuntan al principio de un bloque de 64 ks de memoria o segmento, de ahi lo de la 'S' con la que finalizan todos los registros:

CS: registro segmento de codigo. Establece el area donde se halla el programa en ejecucion.

DS: registro segmento de datos. Especifica la zona donde el programa lee y escribe los datos por defecto.

SS: registro segmento de pila. Especifica el area donde se encuentra la pila del sistema.

ES: registro segmento extra. Se utiliza como una extension del segmento de datos. Es decir, indica otro area de datos aparte del especificado por DS.

4.Puntero de instruccion: IP.

Su longitud es de 16 bits como el resto de registros. Indica la direccion de la siguiente instruccion a ejecutar, y su valor es ajustado durante la ejecucion de la instruccion en curso.

Esta direccion esta en el area de 64 ks de direcciones especificado por CS.

CS e IP en conjuncion conforman la direccion fisica real de la siguiente instruccion a ejecutar.

Mas adelante detallaremos este asunto.

5.Registro FLAGS. O banderas de estado.

Su longitud es de 16 bits. Cada uno de estos bits contiene cierta informacion booleano (verdadero o falso). Segin el valor de cada uno de estos bits sea 1(verdadero) o 0(falso), informar del estado de alguna situacion en particular.

Dentro del registro de FLAGS hay 7 bits que no se utilizan. Los nombres de los utilizados son: Of, Df, If, Tf, Sf, Zf, Af, Pf y Cf.

Estos bits se clasifican en dos grupos:

Flags de estado (Cf, Af, Of, Zf, Pf y Sf): muestran el estado del procesador.

Flags de control ( Df, If, Tf): determinan como el procesador responde a determinadas situaciones. El programador manipular estos bits para controlar el modo de ejecucion de algunas instrucciones.

A continuacion se muestra el significado de cada uno de los flags:

Cf: Bit de Carry (acarreo), se activa (se pone a 1) si se produce acarreo en una operacion aritmetica.

Pf: Bit de paridad, se activa si el resultado de una operacion tiene paridad par, es decir, si el resultado tiene un nº par de unos.

Af: Bit de carry auxiliar, se activa si una operacion aritmetica produce acarreo de peso 16.

Zf: Bit de cero, se activa si una operacion produce 0 como resultado. Suele ser el mas utilizado(mas consultado) por los programadores, por lo menos por mi. Se utiliza para comparaciones de datos y para otras muchas cosas como ciertos bucles,etc.

Sf: Bit de signo, se activa si el bit mas significativo de un resultado es 1. Por convencion cuando se opera con numeros negativos, se utiliza el bit de mayor peso para indicar el signo: si el bit es cero, entonces se trata de un numero positivo, si es 1, se trata de numero negativo. Ya veremos todo esto mas adelante.

Tf: Bit trap o desvio. Si Tf=1, el procesador ejecuta las instrucciones una a una bajo control del usuario. Se pone a 1 este bit para realizar depuraciones del codigo que se esta ejecutando. De esta forma se puedeseguir el flujo del programa.

If: Bit de interrupcion, si vale 1, las interrupciones estan permitidas, y si vale 0, no.

Df: Se usa en las instrucciones que manipulan cadenas de bytes. Segun coloque el programador este bit, a '0' o a '1', las cadenas de bytes seran tratadas en sentido de direcciones crecientes o decrecientes.

Of: Bit de overflow, indica desbordamiento en una operaci¢n aritmetica.

La Memoria:

Los chips de memoria se dedican meramente a almacenar la informacion hasta que se necesita.

El numero y la capacidad de almacenamiento de estos chips que hay dentro del ordenador determinan la cantidad de memoria que podremos utilizar para los programas y los datos. Normalmente la memoria siempre se ha dividido en dos tipos principales, como son la RAM y la ROM.

De todos sera conocida esta distincion y sus carateristicas, asi que no entro en el tema. Si alguien tiene alguna duda, que lo diga. Pues bien, en principio, nosotros vamos a trabajar con el rango de memoria 0 a 1048576, es decir, vamos a trabajar con el primer Mb(megabyte). Existen tecnicas para tratar la memoria que hay en direcciones superiores a estas, pero eso ya se vera en otro momento. Pues bien, en ese Megabyte inicial, tenemos tanto RAM como ROM. La memoria RAM que es la que usamos para almacenar tanto programas como datos empieza en las direcciones bajas y llega hasta el inicio de la ROM. La ROM, evidentemente esta situada en las posiciones altas de memoria, y es ahi donde se almacenan las rutinas mas basicas del ordenador, como las rutinas de acceso a discos, pantalla, etc. Un tema muy importante en el mundo del PC es la denominada barrera de los 640 Ks que seguro habreis oido hablar. Esto quiere decir que aunque tengamos instalados 8 Megabytes en nuestro ordenador, solo podremos ejecutar nuestro programa en las primeras 640 kb. Para poder acceder a los 7 Megabytes restantes debemos utilizar funciones especiales de manejo de memoria extendida, expandida, etc. Pero bueno, eso no nos interesa ahora en absoluto.

Chips inteligentes:

Se trata de chips de apoyo, de los que se sirve el procesador o el usuario. Estos chips existen debido a que el procesador no puede controlar todo el ordenador sin ayuda. Al delegar ciertas funciones de control a otros chips, le queda mas tiempo libre para atender a su propio trabajo. Estos chips de apoyo pueden ser responsables de procesos tales como el flujo de informacion a traves de la circuiteria interna(como el controlador de interrupciones y el controlador DMA) y controlar el flujo de informacion de uno a otro dispositivo(como un monitor o una unidad de disco) concectado al ordenador.

En resumen, estos chips estan ahi para librar de trabajo al procesador.

Bus de direcciones:

El bus de direcciones del Pc utiliza 20 lineas de señal para transmitir las direcciones de memoria y de los dispositivos conectados al bus. Como a traves de cada una de esas 20 lineas pueden viajar dos posibles valores(0 o 1, es decir tension alta o tension baja), el ordenador puede especificar 2^20 (2 elevado a 20) direcciones. Es decir puede direccionar 1 Megabyte. Estamos hablando del 8086 con un bus de direcciones de 20 bits. Modelos superiores como el 80386 o el pentium direccionan muchisimo mas, tanto como les permite su bus: 32 bits en el caso del 386 y 64 bits en el caso del pentium. Es decir, 2^32 y 2^64 direcciones respectivamente.

Bus de datos:

El bus de datos trabaja con el bus de direcciones para transportar los datos a traves del ordenador. Este bus de datos es de 16 bits, al igual que el tamaño de registro. Aunque no tiene que coincidir, como sucede con el procesador 8088, el cual tiene un tamaño de registro de 16 bits y un bus de datos de 8 bits. Esto es asi por simple economia. Por ahorrar costes. Claro que tiene su parte mala, y es que es mas lento al realizar transferencias de datos.

LECCION 2:

DIRECCIONAMIENTO DE MEMORIA EN EL 8086.

SEGMENTACION.

En esta leccion vamos a ver como direcciona la memoria el 8086, es decir, como el 8086 accede a cada una de las posiciones de memoria. La forma en que la CPU construye las direcciones de memoria es muy importante para la programacion del sistema, debido a que constantemente utilizamos instrucciones de transferencias de datos, de acceso a funciones de la BIOS, del DOS, etc. Mas adelante estudiaremos la BIOS. Valga por ahora que es un conjunto de utilidades y procedimientos grabados en la ROM (memoria de solo lectura), los cuales se encargan de acceder al nivel mas bajo en cuanto a programacion se refiere. Es decir, estas rutinas se encargan de manipular el hardware por nosotros. BIOS son las siglas de Basic Input Output System (Sistema basico de entrada/salida). En cuanto al DOS (sistema operativo de disco), decir que aqui nos referi- mos no a las utilidades o comandos que trae consigo, que es lo tipico que se enseña en academias e institutos, sino a la estructura interna del mismo: interrupcion 21h, 24h, etc.

Ya veremos tambien que es una interrupcion.

Bien, antes de entrar de lleno en el tema, conviene saber un poco del por que del mismo. Es decir, que llevo a que fuera de esta forma y no de otra.

A principio de los años 80, Intel (fabricante de la familia de procesadores 80x86) se propuso dar un gran paso adelante con respecto a la competencia. En aquel tiempo los microprocesadores que imperaban entre los ordenadores domesticos eran de 8 bits, es decir, tenian un ancho de bus de datos de 8 bits, el tamaño de palabra de memoria era de 8 bits, y los registrosdel procesador eran de 8 bits. Un claro ejemplo de esto fue el microprocesador Z80 (de la empresa Zilog), el cual estaba incorporado en maquinas tan famosas como los spectrum, amstrad, msx, etc. Como he dicho, el ancho del bus de datos era de 8 bits. Esto quiere decir que todas las transferencias de datos que se hicieran se harian de 8 en 8 bits, es decir, byte a byte. Pues bien, aunque el microprocesador era de 8 bits, y la mayoria de registros tambien lo fuera, habia alguno mayor (16 bits). Me estoy refiriendo sobre todo al registro de direcciones que era de 16 bits. De esta forma, un amstrad cpc464 podia acceder a 64 ks de memoria. 64 Ks es la maximo que podia direccionar el z80 original.En ese momento Intel se planteo superar esa barrera de las 64 Ks, pero tenia un problema. El z80 por ejemplo, habia conseguido tener registros de 16 bits cuando el microprocesador es de 8. Pero pasar de 16 bits de capacidad en registros en aquellos momentos no era posible para los microprocesadores. Es decir, no habia suficientes avances tecnologicos como para conseguir tamaños de registros mayores en un microprocesador. De tal manera que habia que buscar una formula diferente... Y ahi es cuando surgio el tema de los segmentos que tantos quebraderos de cabeza a dado hasta ahora y sigue dando.

A Intel se le ocurrio la idea de construir una direccion de 20 bits de ancho y colocarla en el bus de direcciones para poder dirigirse a la memoria. Pero al ser los registros de 16 bits, solo habia una solucion posible para crear este ancho de 20 bits: Usar 2 registros de 16 bits!!!

El 8086 divide el espacio de direcciones (1 Mbyte) en segmentos, cada uno de los cuales contiene 64 Ks de memoria (la maxima direccionable por un solo registro). Entonces, para direccionar una posicion de memoria nos valemos de dos registros: Registro de segmento y de offset.

Ya vimos en la leccion anterior que habia varios registros de segmento: cs (registro de segmento de codigo), ds (de datos), etc.

Pues bien, este primer registro (de segmento), indica donde comienza el trozo de 64 Ks que buscamos. Y el segundo registro (el de offset), contiene el desplazamiento dentro de ese segmento.

Bien. Hemos visto que son necesarios 2 registros para direccionar ese Mbyte de memoria, y tenemos un bus de direcciones de 20 bits.

Esto nos conduce a que el microprocesador debe realizar unas operaciones sobre estos dos registros para obtener la direccion fisica de 20 bits.

Esto se logra de la siguiente manera:

El 8086 mueve el valor del segmento 4 bits a la izquierda y le suma el valor del desplazamiento para crear una direccion de 20 bits.

El primer registro, es el de segmento (en este caso, segmento DS, de datos). El segundo registro es el de offset o desplazamiento. En este caso utilizamos el registro BX para direccionar dentro de el segmento. Podiamos haber utilizado tambien el registro SI, el DI,etc.

A partir de estos dos registros, debemos acceder a una posicion de memoria fisica dentro del Mbyte de que disponemos para el 8086. Pongamos que el registro DS tiene el valor 0B800h (en hexadecimal) (podeis utilizar SBCALCU de SAN BIT para hacer los cambios de base, y trabajar con bases diferentes a la decimal. Tambien para la decimal, por supuesto).

Y el registro BX contiene el valor 0037h.

Tenemos pues (en binario):

DS: 1011100000000000 BX: 0000000000110111

Para obtener la direcci¢n fisica de memoria, y teniendo en cuenta todo lo dicho relativo a segmentos, el microprocesador acturaria as¡:

(Graficamente)

Haria una suma de la siguiente forma:

DS: 1011100000000000

BX: + 0000000000110111

------------------------------

10111000000000110111

Obteniendo asi la direcci¢n de 20 bits necesaria para cubrir todo el Mbyte.

Si ese numero (10111000000000110111) que esta en binario, lo pasamos a hexadecimal, tenemos que la direccion fisica correspondiente a la anterior segmentada es: 0B8037h.

De todo lo anterior, se desprende que los segmentos empiezan siempre en direcciones divisibles por 16. Mas tecnicamente: cada segmento comienza en una direccion de parrafo. Un parrafo son 16 bytes.

Por supuesto nunca habra un segmento que empiece en una direccion impar, por ejemplo. Como ejemplo: El primer segmento posible empieza en la direccion fisica 0. El segundo empieza en la direccion Esto es mas complejo de lo que parece. Si tienes alguna duda, ya sabes... Si le das vueltas a la idea, te daras cuenta que diferentes combinaciones de direcciones segmentadas dan una misma direcci¢n fisica.

Tambien se puede apreciar que los segmentos se pueden superponer unos a otros, pueden ser identicos, o pueden encontrarse en partes totalmente lejanas en la memoria.

LECCION 3:

CHIPS DE APOYO (Ampliacion de la leccion 1).

Ya vimos en la primera leccion que se entendia por chips de apoyo, soporte, etc. Tambien llamados controladores, ya que controlan una parte del hardware para ir aligerando el trabajo de la CPU. De esta forma la CPU tiene mas tiempo para la ejecucion del programa correspondiente. En muchos casos, estos chips son programables. Por supuesto, estos chips pueden ser programados por el programador en ensamblador (valga la redundancia), con lo cual no trabajan por su cuenta, sino que aceptan las instrucciones que les hacen funcionar a traves de la CPU.

A continuacion se da una relacion de los diferentes chips de apoyo o controladores del Pc:

El controlador programable de interrupciones (chip 8259)

En un Pc, una de las tareas esenciales de la CPU consiste en responder a las interrupciones del hardware. Una interrupcion del hardware es una señal generada por un componente del ordenador que indica que ese componente requiere la atencion del procesador. Por ejemplo el reloj del sistema, el teclado, y los controladores de disco, generan interrupciones de hardware en un momento dado para que se lleve a cabo su tarea. En ese momento, la CPU responde a cada interrupcion, llevando a cabo la actividad de hardware apropiada, ejecutando lo que se llama rutina de atencion a la interrupcion, que es una porcion de codigo que se ejecuta como respuesta a una peticion de interrupcion.

Tomemos como ejemplo el teclado. (Puede ser conveniente leer antes el apartado 'Interrupciones', que viene desarrollado mas abajo). El usuario pulsa una tecla. Inmediatamente, la circuiteria digital del periferico detecta la pulsacion de la tecla y almacena su "codigo de rastreo" (toda tecla tiene asociado un codigo de 8 bits denominado scan code) en un registro reservado para tal fin, llamado puerto de teclado. (Mas adelante, al hablar de puertos, se amplia la informacion). Entonces, el teclado activa una linea de peticion de interrupcion, mas concretamente, la linea IR1 del 8259. (IR son las siglas de Interrupt Request, o peticion de interrupcion. Tambien se puede decir IRQ, que es a lo que estamos mas acostumbrados, sobre todo cuando instalamos una tarjeta de sonido o algo por el estilo).

A continuacion, el 8259 activa el pin INTR de la CPU. (El pin INTR se  activa cada vez que se produce una peticion de interrupcion, es una linea externa que comunica al Procesador con el exterior).

Por ultimo, y resumiendo mucho, la CPU termina la instruccion en curso, y ejecuta la rutina de atencion a la interrupcion. Al terminar de ejecutar esta rutina, el control vuelve a la siguiente instruccion por donde se habia quedado en el programa en curso. Todos los registros deben tener el valor que tenian antes de ejecutar dicha rutina.

El controlador programable de interrupciones se llama a menudo por sus siglas: PIC.

El controlador DMA (chip 8237).

Algunas partes del ordenador son capaces de transferir datos hacia y desde la memoria, sin pasar a traves de los registros de la CPU. Esta operacion se denomina acceso directo a memoria o DMA (Direct Memory Access), y la lleva a cabo un controlador conocido como controlador DMA.

El proposito principal de dicho controlador, es el de permitir a las unidades de disco leer y escribir datos prescindiendo de pasar por los registros del microprocesador. De esta forma, las transferencias de datos se hacen mas rapidas. Pero esto es solo en teoria, ya que con los modernos procesadores que cuentan con una frecuencia de proceso varias veces mas rapida que la del bus, el controlador DMA, apenas ofrece ninguna ventaja.

El Interface de periferia (chip 8255).

El interface de periferia crea una conexion entre la CPU y los dispositivos perifericos como el teclado y el altavoz. Actua como una especie de intermediario utilizado por la CPU para comunicar determinadas señales al dispositivo deseado.

El generador de reloj (chip 8248).

Este generador suministra las señales de reloj que coordinan el microprocesador y los perifericos. Produce una señal oscilante de alta frecuencia. Por ejemplo, en el IBM PC original esta frecuencia era de14,31818 megahercios o millones de ciclos por segundo. No hay que confundir esta frecuencia con la frecuencia del procesador. Otros chips que necesitan una señal de tiempo regular, la obtienen del generador de reloj, dividiendo la frecuencia base por una constante para obtener la frecuencia que necesitan para realizar sus tareas. 

Por ejemplo, el 8088 del IBM PC, funcionaba a 4,77 MHz, una tercera parte de la frecuencia base. El bus interno del IBM PC y el temporizador utilizan una frecuencia de 1,193 MHz, es decir, un cuarto del ratio del 8088 y una doceava parte del ratio base.

El temporizador o timer (chip 8253).

Este chip genera señales de tiempo a intervales regulares controlados por software. Esto es, que podemos cambiar la frecuencia de estos intervalos por medio de un programa. El timer dispone de varias lineas de salida, funcionando cada una con una frecuencia independiente a las otras, y conectadas cada una a otros componentes del sistema.

Una funcion esencial del contador es la de generar un tic-tac de reloj que mantenga actualizada la hora del dia. Otra de las señales producidas por el contador puede ser utilizada para controlar la frecuencia de los tonos producidos por el altavoz del ordenador.

El controlador de video (chip 6845).

El controlador de video, al contrario del resto de chips de apoyo presentados hasta ahora, no se encuentra en la placa madre del PC, sino que esta depositado en una tarjeta de video colocada en una ranura de ampliacion. Es el corazon de las tarjetas de video CGA, EGA, VGA, etc.

Controladores de entrada/salida.

Los PCs tienen varios subsistemas de entrada/salida con circuiteria de control especializada que proporciona un interfaz entre la CPU y el hardware de E/S. Por ejemplo, el teclado tiene un chip controlador propio que transforma las señales electricas producidas por las pulsaciones de teclas en un codigo de 8 bits que representa la tecla pulsada. Todas las unidades de disco disponen de circuiteria independiente que controla directamente la unidad. La CPU se comunica con el controlador a traves de un interfaz. Los puertos serie y paralelo tambien disponen de sus propios controladores de entrada/salida.

Los coprocesadores matematicos (8087/80287/80387).

Son utilizados en caso de estar disponibles en el ordenador, para trabajar con numeros en coma flotante y coma real, cosa que el 8086 no puede hacer.

Todos estos chips, se conectan entre si, a traves del BUS, que ya sabemos en que consiste.

LECCION 4:

ENTRADA/SALIDA (COMUNICACION CON EL HARDWARE I).

La comunicacion entre un programa y el hardware, es decir los chips de apoyo y las tarjetas de ampliacion, se efectua mediante los llamados Ports (puertos, en castellano). Estos puertos son zonas de memoria de 1 o 2 bytes de tamaño, en las cuales se depositan los datos que van a ser utilizados por los chips o tarjetas, y tambien se depositan los datos que devuelven estos chips o tarjetas al procesador.

En el Pc, existe una zona de memoria de 64 Ks, ajena a la memoria principal, dedicada a los puertos. Es decir, estos 64 Ks de memoria no tienen nada que ver con la memoria disponible para los programas. Para realizar los movimientos de datos entre puertos y procesador existen instrucciones especiales: IN y OUT. Tanto IN como OUT tienen dos formas de uso, las cuales utilizan el registro AL o AX para almacenar los datos a leer o escribir. La forma difiere en funcion de que se quiera acceder a un puerto menor de 256 o mayor.

+ M‚todo directo o estatico: corresponde cuando se quiere acceder a un puerto menor de 256.

IN AL,10H > lee un byte del puerto 10h

IN AX,10H > lee una palabra del puerto 10h

OUT 0FFH,AL > escribe el valor de AL en el puerto 0FFH

+ M‚todo inndirecto o dinamico: corresponde al caso de querer acceder a un puerto mayor de 256, para lo cual se utiliza el registro DX indicando el numero de puerto.

IN AL,DX > lee un byte del puerto indicado por DX.

(Antes hemos tenido que introducir en DX la direccion del puerto).

OUT DX,AX > escribe la palabra contenida en AX en el puerto DX.

Algunos ejemplos de puerto son:

60H > acepta entradas de teclado.

61h > controla el altavoz.

3F0H:3F7H > Opera sobre la controladora de discos.

En el PC cualquier subsistema, exceptuando la memoria, esta controlado por el procesador a traves de los puertos.

INTERRUPCIONES (COMUNICACION CON EL HARDWARE II).

Las interrupciones constituyen la forma en que la circuiteria externa informa al microprocesador de que algo ha sucedido (como que se ha pulsado una tecla, por ejemplo) y solicita que se emprenda alguna accion. Pero no acaba ahi su utilidad, ya que las interrupciones ademas son el medio principal de comuniccion entre las funciones de la BIOS y el DOS.

En este segundo caso, son mal llamadas interrupciones. Mas bien habria que decir funciones, ya que nos sirven para hacer una llamada a una funcion BIOS o DOS, como por ejemplo la accion de cambiar de modo de video, para la cual se utiliza la interrupcion 10h (Driver o controlador de video), con el numero adecuado de funcion. Mas adelante veremos como llamar a una funcion.

Al primer tipo de interrupciones se les denomina interrupciones de hardware, y son las interrupciones reales. Esto es, que estando un programa en ejecucion, se interrumpe ‚para ejecutar un trozo de codigo necesario para atender a la peticion de un dispositivo, como puede ser el teclado. Acto seguido, se reanuda la ejecucion del programa en cuestion.

Son las interrupciones que vimos en la leccion 3, al hablar del PIC. 

Al segundo tipo de interrupciones se les denomina interrupciones de software, y son las 'ficticias', ya que no hay ningun dispositivo pidiendo atencion del procesador, sino que es el programa del usuario el que ejecuta una funcion BIOS o DOS, mediante una interrupcion.

En este caso, no se interrumpe el programa de forma subita, sino que es dicho programa el que lanza una interrupcion, la cual tiene su rutina de atencion a la interrupcion (como vimos en la lecci¢n 3) 'conectada' a un grupo de funciones o rutinas.

Veamos las interrupciones con mas detalle:

+ La tabla de vectores:

Toda interrupcion aceptada conduce a la ejecucion de un subprograma especifico, como hemos visto. Pero como sabe el procesador donde empieza este subprograma, una vez que atiende a la interrupcion... La respuesta nos la da la tabla de vectores.

Esta tabla de vectores contiene las direcciones de comienzo o punteros al subprograma de atencion a la interrupcion.

La tabla esta compuesta de 256 entradas. Es decir, son posibles 256 interrupciones diferentes en el PC.

Cada una de estas entradas, contiene la direccion de inicio del codigo de atencion a una interrupcion, en la siguiente forma: 2 primeros bytes (una palabra) que contienen la direccion base del segmento, y los 2 ultimos bytes que contienen el desplazamiento.

En total 4 bytes para indicar el comienzo de una interrupcion, en la forma segmento:desplazamiento.

Ya vimos en la segunda leccion como transformar una direccion segmentada (segmento:desplazamiento) en una direccion fisica o real.

Durante la aceptacion de una interrupcion, el 8086 carga la direccion base del segmento en el registro CS y el desplazamiento en el contador de programa IP. De esta forma, la siguiente instruccion a ejecutar, que viene dada por los registros CS:IP, ser la primera del subprograma de atencion a la interrupcion.

+ Pines (lineas de bus) para demandar interrupcion  desde el exterior.

Existen 3 lineas externas jerarquizadas que son, por orden de prioridades decrecientes: RESET, NMI e INTR. Solo INTR es enmascarable (cuando un pin de demanda de interrupcion esta enmascarado "inhabilitado" la activacion del pin, no produce ninguna interrupcion).

Es decir, que si se activan los pines RESET o NMI, siempre van a conducir a la ejecucion de una interrupcion. Pero si se activa el pin INTR, tenemos dos opciones (dependiendo de si esta enmascarado o no), que son hacer caso omiso de la peticion de interrupcion, o atender dicha interrupcion, respectivamente.

Pin INTR:

Una peticion de interrupcion sobre este pin es enmascarable mediante el bit IF (bandera de interrupcion) del registro FLAGS o registro de estado.

Este bit IF, es la mascara de INTR. Para saber si esta enmascarada o no la linea INTR, se mira este flag. El cual puede tener (obviamente) dos valores: 0 y 1. Enmascarado es 0.

Para manipular este bit, disponemos de dos instrucciones en ensamblador:

CLI (Clear IF, o borrar flag IF) que lo pone con valor 0.

STI (Set IF, o activar flag IF) que lo pone con valor 1.

La peticion de interrupcion se realiza activando el pin INTR con nivel

alto (1) y debe mantenerse asi hasta que por el pin INTA (pin asociado

al pin INTR. Es activo a nivel bajo (0), indicando que se ha aceptado

la interrupcion solicitada por medio del pin INTR) el 8086 indique que ha sido aceptada.

Entonces... Contamos con el pin INTR para pedir al procesador atencion a una interrupcion, y con el pin asociado INTA que esta con valor (0) cuando la interrupcion haya sido aceptada.

INTR > Interrupt Request (peticion de interrupcion).

INTA > Interrupt Accepted (interrupcion aceptada).

Veamos c¢mo actua la CPU desde que se activa el pin INTR hasta que se retorna del subprograma de atencion a la interrupcion:

Debido a que la interrupcion interrumpir al programa en ejecucion en cualquiera de sus instrucciones, es necesario resguardar el contenido del registro de estado (FLAGS), para que al volver de la interrupcion, tengan las banderas el mismo valor.

Y sobre todo, hay que guardar la direccion de la siguiente instruccion a ejecutar en el programa actual.

Pero donde se guardan todos estos datos... En una zona de memoria denominada PILA, la pila del procesador. (Explicacion en el ultimo apartado de esta leccion).

Al acto de introducir un dato en la pila se le denomina apilar, y a sacarlo de la misma se le denomina desapilar. Pues bien, el procesador hara lo siguiente: 

Apila el contenido del registro de estado (flags)

Apila la direccion de retorno (contenido de los registros CS e IP).

Inhibe las interrupciones (IF=0 y TF=0, mas adelante se comenta la utilidad del flag TF o TRACE).

Esto se hace para que no se produzca otra interrupcion durante la secuencia de aceptacion de la interrupcion. Esto es muy importante. 

Activa el pin INTA (lo pone a nivel bajo). El dispositivo que ha solicitado la interrupcion, al notar el cambio en el pin INTA, queda enterado de la aceptacion de la interrupcion. Lee el numero del vector de interrupcion del bus de datos. Previamente, el dispositivo lo ha depositado en respuesta a la activacion del pin INTA. Obtiene la direccion del subprograma de atencion a la interrupcion. Dicha direccion se encuentra (como hemos visto antes) almacenada en la tabla de vectores.

El 8086 ejecuta la subrutina que finaliza con la instrucci¢n IRET, o Retorno de Interrupcion, cuya ejecuci¢n restituye en CS e IP la direccion de retorno salvada en la pila, y en el registro de estado el valor de los flags.

Al restaurar los flags, se anula la inhibici¢n anterior de IF y TF, con lo cual, otra vez se aceptan interrupciones. Pudiendo asi tener interrupciones en cascada. 

Pin NMI:

Este pin esta reservado a acontecimientos graves, como puede ser un corte de corriente, un error de memoria, del bus, etc.

La activaci¢n de NMI no conlleva ninguna lectura en el bus de datos del nº de vector de interrupcion, sino que la CPU directamente busca el vector de interrupcion numero 2.

Pin RESET:

Inicializa el sistema.

En la peticion de RESET no se almacena nada en la pila ni se accede a la tabla de vectores para conseguir la direccion de comienzo.

Al activar el pin RESET, el registro de estado queda borrado (0).

CS = 0FFFFh.

IP = 00000h.

De esta manera, la siguiente instruccion a ejecutar por el procesador es la contenida a partir de FFFF:0, codigo de reinicializacion y carga del sistema operativo. Son los ultimos bytes de la ROM.

El resto de registro de segmentos quedan con valor 0.

DS = 0000

ES = 0000

SS = 0000

+ Interrupciones internas o desvios.

El microprocesador 8086 tiene 2 interrupciones internas: 'Division imposible' y 'funcionamiento paso a paso (TRACE)'.

Division imposible:

Se produce cuando se divide por 0, o cuando el cociente resultante de la division no cabe en el registro preparado para contenerlo.

En ambos casos, se ejecuta la interrupcion 0.

Funcionamiento paso a paso:

Si el programador coloca a (1) el bit TF (TRACE) del registro de estado, al final de cada instruccion, la CPU bifurcar a la posicion de memoria indicada por el vector de interrupcion numero 1.

Esto es lo que utilizan los debuggers o depuradores de codigo para hacer un seguimiento del programa, instruccion por instruccion.

Mas adelante, cuando hablemos acerca de la programacion de utilidades residentes, entraremos en la programacion practica de las interrupciones.

Valga lo dicho hasta ahora como base teorica.

La pila del procesador:

La pila es una caracteristica interna del 8086. Es una estructura de datos situada en la RAM. Proporciona a los programas un lugar donde almacenar datos de forma segura, pudiendo compartirlos con otros procedimientos o programas de forma comoda y practica.

La funcion mas importante de la pila es la de mantener las direcciones de retorno en las llamadas a procedimientos e interrupciones, asi como guardar los parametros pasados a estos procedimientos. La pila tambien se utiliza para almacenamiento temporal de datos dentro de un programa, y para muchas cosas mas que se aprenden con la practica. La pila tiene su nombre por analogia con los montones de platos apilados (pilas de platos). Cuando un dato nuevo es introducido en la pila, se dice que es apilado (push) debido a que se situa por encima de los demas, es decir se situa en la CIMA de la pila.

Una pila opera en el orden ultimo en entrar primero en salir:

LIFO (LAST IN FIRST OUT) o lo que es lo mismo, el ultimo en entrar es el primero en salir. Esto significa que cuando la pila se utiliza para seguir la pista de los retornos de las subrutinas, la primera llamada a subrutina que se hizo, es la ultima que se devuelve. De esta manera, la pila mantiene ordenado el funcionamiento del programa, las subrutinas y rutinas de tratamiento de interrupcion, sin importar la complejidad de la operacion.

La pila crece en orden inverso. Es decir, a medida que se añaden nuevos datos, la cima de la pila se acerca mas a posiciones mas bajas de memoria.

Existen 3 registros destinados a gestionar la pila.

Registro de segmento de pila (SS): que indica la direccion base del segmento de pila

Puntero de pila (SP): que apunta a la cima de la pila.

Puntero base de pila (BP): que se usa para moverse a traves de la pila sin cambiar la cima. Se suele utilizar para acceder a los distintos parametros al llamar a una funcion. Los elementos que se almacenan en la pila son del tipo palabra (2 bytes). Esto quiere decir, entre otras cosas, que el puntero de pila (SP), asi como el puntero base de pila (BP), incrementan/decrementan en 2 su valor para apuntar a un nuevo elemento dentro de la pila, fruto de apilar o desapilar un elemento.

Tambien conlleva el que si queremos almacenar un byte en la pila, primero lo debemos convertir en palabra (2 bytes), y luego almacenar esa palabra. Esto es muy sencillo, solo hay que meter ese byte o registro de 8 bits en un registro de 16 bits y almacenar este registro. Las instrucciones para manejar la pila son:

PUSH > Guarda un dato en la pila. Decrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir.

Ejemplo: PUSH AX > Apila el contenido de AX en la cima de la pila.

POP > Obtiene un dato de la pila. Incrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir.

Ejemplo: POP AX > Desapila el contenido de la cima de la pila en el registro AX. Es decir, AX contendra el valor que hubiera en la cima de la pila, y el puntero de pila se actualiza incrementandolo en 2.

PUSHF > Guarda el contenido del registro de estado (FLAGS) en la pila. Decrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir.

No es necesario indicar sobre que actua esta instruccion, lo lleva implicito en su nombre PUSHF (PUSH FLAGS).

POPF > Introduce en el registro FLAGS el contenido de la cima de la pila. Incrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir.

Al igual que con la instruccion anterior, no es necesario indicar sobre que actua esta instruccion POPF (POP FLAGS).

Conviene recordar el hecho de que la pila crece en orden inverso al normal, es decir de direcciones de memoria altas a direcciones bajas. Por lo tanto es necesario tener en cuenta el uso que se va a hacer de la pila en el programa, debido a que si reservamos espacio en nuestro programa para una pila pequeña, en caso de sobrepasarla haciendo muchos push seguidos, machacaria nuestro programa.

Hay que tener en cuenta que no solo es nuestro programa el que utiliza la pila mediante la instruccion PUSH y mediante llamadas a procedimientos, interrupciones, etc. Sino que mientras nuestro programa corre se estan sucediendo numerosas interrupciones que conllevan muchos PUSH.

Por ejemplo, 18'2 veces por segundo se produce la interrupcion de reloj, con lo cual, todas estas veces se esta apilando y posteriormente quitando informacion de la pila.

Por regla general, basta con tener una pila de unos 2 KS, es decir, espacio para almacenar 1024 elementos. Es muy dificil que se sobrepase este tamaño.

LECCION 5:

CODIFICACION DE LAS INSTRUCCIONES EN EL 8086.

(Este apartado es muy tecnico. Aunque no es imprescindible comprender lo que se expone a continuacion para programar en ensamblador, es muy util conocer como el procesador interpreta lo que le 'pedimos'. Esto nos da un mayor conocimiento acerca de la maquina en cuestion. Y de esta forma entendemos el porque‚ de ciertas sintaxis de instrucciones. Y resolveremos mas facilmente los errores una vez que se nos presenten).

Cada procesador tiene un conjunto de instrucciones para manejarlo, asi como para manejar la maquina por medio de el.

Indistintamente del lenguaje de programacion que estemos utilizando, cuando obtenemos el ejecutable, este esta compuesto unicamente por ese tipo de instrucciones basicas (instrucciones de codigo maquina). Dependiendo de la calidad y prestaciones de ese lenguaje de programacion, el codigo resultante, necesitar mas instrucciones del procesador o menos.

De todos es conocido, que hay lenguajes de alto o medio nivel (como C, pascal, basic, etc.) en los que para una misma tarea, uno dara un ejecutable mas grande que otro. Velocidad, aparte. Esto no sucede asi con ensamblador, en el que para cada instruccion,existe una y solo una instruccion en codigo maquina. 

Pues bien, ahora vamos a ver la estructura de esas instrucciones basicas o de codigo maquina. Las instrucciones del 8086 se codifican sobre 4 campos como maximo, y tienen un tamaño de 1 a 6 bytes.

Es decir, dependiendo de la instruccion de que se trate, necesitar mas o menos bytes para su codificacion, asi como mas o menos campos.

Los cuatro campos en una instruccion codigo maquina son:

1. Codigo de operacion: Este campo siempre aparece (obviamente). Una vez que el procesador descifra el significado de este campo, sabe si la instruccion consta de mas campos o si se trata de una instruccion de un solo campo.

2. Modo de direccionamiento (byte EA): Le indica al procesador el numero de operandos que acompañan al codigo de operacion, asi como el tipo de estos operandos(registros, memoria, valor inmediato).

3. Desplazamiento del dato (sobre 8 o 16 bits): En caso de existir este campo, supone un desplazamiento sobre la direccion dada por un registro indice o base (especificado este registro mediante el byte EA).

4. Valor inmediato (sobre 8 o 16 bits): Almacena un valor numerico de 8 o 16 bits, que va a ser utilizado para una transferencia, una operacion aritmetica, etc. 

Ahora entramos un poco mas en detalle:

Primero veremos un esquema de una instruccion codigo maquina:

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿

³ 8 bits 2 3 3 8 ¢ 16 bits 8 ¢ 16 bits ³

³ ÉÍÍÍÍÍÍÍÍÍÍÍ» ÉÍÍÍÑÍÍÍÑÍÍÍ» ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³

³ º c¢digo de º º º º º º Valor º ³

³ º operaci¢n º ºMOD³REG³R/Mº ºDesplazamientoº º Inmediato º ³

³ ÈÍÍÍÍÍÍÍÍ1/4 ÈÍÍÍÍÍÍÏÍÍÍ1/4 ÈÍÍÍÍÍÍÍÍÍÍÍÍ1/4 ÈÍÍÍÍÍÍÍÍÍÍÍÍ1/4 ³

³ 1 byte 1 byte 1 ¢ 2 bytes 1 ¢ 2 bytes ³

ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

El codigo de operacion esta codificado sobre 8 bits.

Por medio de este campo se sabe si va a ser necesario cualquier otro de los tres restantes. Tambien el codigo de operacion contiene informacion acerca de si se va a trabajar con palabras o con bytes.

Byte EA o Modo de direccionamiento: Contiene 3 campos. Los campos MOD y R/M especifican el modo de direccionamiento, y el campo REG especifica el registro de que se trata en la instruccion.

El campo MOD que es de 2 bits puede tener 4 valores diferentes: Los 3 primeros seleccionan el desplazamiento en los modos de direccionamiento de memoria. El cuarto selecciona un registro. Detallemos la funcion de estos bits en cada una de las 4 posibilidades:

00 > No hay desplazamiento.

01 > Se usa un byte para codificar el desplazamiento.

10 > Se usan 2 bytes (una palabra) para codificar el desplazamiento.

11 > Hace que R/M seleccione un registro usando la misma codificacion de los registros que para REG (ver mas abajo), en lugar de un modo de direccionamiento de la memoria.

Es decir, que se produce una transferencia de un registro a otro.

El campo REG que es de 3 bits codifica el registro empleado. Por tanto es posible especificar hasta 8 registros diferentes por medio de este campo. Dependiendo de que se trate de acceso a palabras o a octetos, se seleccionar un registro de entre un grupo de 8, o de un segundo grupo de 8 registros.

Para cuando se accede a registros de 16 bits, el campo REG codifica los registros de palabra de la siguiente manera:

AX (000), CX (001), DX (010), BX (011)

SP (100), BP (101), SI (110), DI (111)

Cuando se accede a registros de 8 bits, la codificacion de los registros de tamaño byte queda como sigue:

AL (000), CL (001), DL (010), BL (011)

AH (100), CH (101), DH (110), BH (111)

El campo R/M indica el segundo registro (si lo hay) o el tipo de direccionamiento a memoria.

En caso de que haya segundo registro, este se codifica de la misma forma que para el campo REG.

En caso de que se trate de un modo de direccionamiento de memoria, estos tres bits seleccionan uno de los modos de direccionamiento posibles de acuerdo con la siguiente tabla:

000 desplazamiento final = [BX] + [SI] + desplazamiento

001 desplazamiento final = [BX] + [DI] + desplazamiento

010 desplazamiento final = [BP] + [SI] + desplazamiento

011 desplazamiento final = [BP] + [DI] + desplazamiento

100 desplazamiento final = [SI] + desplazamiento

101 desplazamiento final = [DI] + desplazamiento

110 desplazamiento final = [BP] + desplazamiento

111 desplazamiento final = [BX] + desplazamiento

El desplazamiento en caso de existir, supone un incremento en la direccion dada por un registro indice o base, dando lugar asi a un desplazamiento final, dentro de un segmento dado. Es decir, como se ve en la tabla superior, podemos acceder a memoria a traves de un registro base (BX) o un registro indice (SI, DI), etc, o bien hacerlo a traves de uno de esosregistros, pero ayudandonos de un desplazamiento que se suma a la direccion que tienen establecida esos registros.

Veremos mas adelante la utilidad de utilizar desplazamientos sobre un registro base o indice.

Como ejemplo: Tenemos el registro DI apuntando a (con valor igual a) la direccion 3000h (direcciones siempre en hexadecimal).

En esa direccion tenemos el comienzo de una cadena de caracteres que queremos convertir a mayusculas. Y una vez que los hemos convertido, los queremos copiar a la memoria de pantalla.

Pues bien, podemos ir incrementando DI para tratar cada uno de estos caracteres, o bien podemos utilizar DI junto con un desplazamiento para acceder a cada uno de los caracteres. Es decir, para acceder al primer elemento seria DI+0, para el segundo, seria DI+1, etc. De esta forma, al terminar la tarea, DI seguira apuntando al principio de la cadena, y podremos copiar la cadena desde el principio a donde corresponda. Si no utilizaramos desplazamiento, tendriamos que tener una variable apuntando al inicio de la cadena, para tenerlo luego localizable. Bueno... Esto es un simple ejemplo. Las posibilidades que nos ofrece el utilizar desplazamientos acompañando al registro base o indice son mucho mas interesantes que lo que acabamos de ver en el ejemplo.

El valor inmediato se utiliza cuando hacemos movimientos de datos a registros o a memoria. Por ejemplo queremos introducir en el registro AX la cantidad 37867 (93EBH), pues ese 37867 seria el valor inmediato.

En ensamblador la instrucci¢n seria:

MOV AX,37867

Simple, no? Mover (MOV) la cantidad 37867 al registro AX.

Proximamente se vera el resto de instrucciones en ensamblador,

mientras tanto, y por ser necesario ahora, aprenderemos el uso de la instruccion MOV.

La instruccion como hemos podido ver, se utiliza para movimientos o transferencias de datos: de registro a registro, de registro a memoria, y de memoria a registro. Pero nunca de memoria a memoria, ya que laarquitectura del procesador y bus no lo permiten.

La sintaxis basica de la instruccion es la siguiente:

MOV destino,fuente.

El destino siempre a la izquierda, y la fuente a la derecha.

Ejemplos:

MOV ax,5 > mueve el valor inmediato (o dato) 5 al registro AX.

Examinemos esta instruccion. Alguien podria pensar que como el valor 5 cabe en un solo registro de 8 bits (AL en este caso), el registro AH quedaria como estaba antes de la instruccion. Pues no es asi.

Si le decimos al procesador que introduzca un 5 en AX, asi se hara.

Poniendo a cero el registro AH, para que AX tenga el valor 5.

Veamos como se codifica esta instruccion:

MOV AX,5 > B8 05 00 (C¢digo maquina, siempre en hexadecimal).

En primer lugar tenemos el primer byte que contiene el codigo de operacion (B8).

Debido a que este codigo de operacion(B8) tiene implicita la utilizacion del registro AX como destino, no es necesario el byte EA o byte de direccionamiento, que si seria necesario para transferencias con otros registros. Como vimos en la primera leccion al hablar de registros, el registros AX (AH, AL) se utiliza normalmente como acumulador, de tal manera que existen operaciones especiales para trabajar con el, como la instruccion B8 y otras muchas de movimiento de datos, en las que no se especifica el registro mediante el byte EA, ya que esta implicito en el codigo de operacion. De esta manera se gana velocidad en la ejecucion del programa utilizando los registros para lo que han sido creados. AX acumulador, CX contador, etc.

Despues del codigo de operacion tenemos dos bytes (1 palabra).

Estos dos bytes forman el campo Valor Inmediato, que como vemos aqui es de 16 bits.

Como os habreis dado cuenta, de los 4 campos que puede tener una instruccion codigo maquina, esta solo tiene dos:

El primero (codigo de operacion), y el ultimo (valor inmediato).

Y volviendo de nuevo al campo Valor inmediato y a su tamaño en esta instruccion (2 bytes):

El orden de estos bytes es muy significativo. Veamos...

Tenemos el valor 5 para introducir en una palabra. Lo normal seria que en el codigo se almacenara este cinco como (00 05), pues en el 8086 esto no es asi. Como siempre, para acelerar el programa cuando se manejan transferencias de datos, se llego a la conclusion de que si se almacenan los bytes que componen una palabra en orden inverso al normal, luego es mucho mas rapido recuperarlos. Y es asi como se hace en la practica. Cada vez que almacenamos una palabra en memoria, el byte de mayor peso queda a la derecha del byte de menor peso.

De lo anterior se desprende que el numero 5 al introducirlo en una palabra de memoria, quedaria como (05 00).

Otro ejemplo: Una vez que almacenamos el numero 8BC3H en memoria, si hacemos un volcado de memoria para ver que tenemos, veremos que en memoria no esta el numero como 8BC3H, sino que nos encontramos con C38BH.

MOV al,5 > Introduce el valor 5 en el registro AL.

En este caso, si que AH queda como estaba antes de la instruccion, ya que en la misma no interviene tal registro de ninguna forma (ni implicita al referirse a AX, ni explicita al referirnos a al en concreto).

La instruccion se codifica como:

MOV AL,5 > B0 05

Este ejemplo es practicamente como el anterior, excepto que el codigo de operacion en vez de ser B8 es B0, y ademas ya no hay 2 bytes en el campo valor inmediato, sino que hay uno solo, ya que vamos a introducir el dato en un registro de tamaño byte.

Ejemplo cuando se trata de transferencias entre registros:

MOV CX,SI > Introduce el valor del registro SI en el registro CX.

La instruccion se codifica como:

MOV CX,SI > 8B CE

En esta instruccion tenemos un codigo de operando y el byte EA.

Mediante este byte EA el procesador sabe que registros intervienen en la transferencia.

Descomponiendo el byte EA en sus digitos binarios, tenemos:

CE > 11001110

El campo MOD con valor 11, hace que R/M seleccione un registro como fuente.

El campo REG con valor 001, indica que el registro destino es CX.

El campo R/M con valor 110, indica que el registro fuente es SI.

Hemos visto la manera de introducir un dato en un registro. ¨Pero como hacemos para introducir un dato en memoria? Bien, para esto se utilizan las variables (que tambien existen en ensamblador) o bien, se indica una posicion de memoria concreta, pasando de variables.

Hay una tercera manera que es utilizar registros indice o base.

+ En el primer caso, es muy simple. Si queremos introducir el valor 70h en la variable X, basta con escribir MOV X,70h. 

Previamente la variable X la hemos definido y hemos definido tambien su tamaño: byte, palabra, doble palabra.

Una vez que el compilador de el codigo ejecutable, lo que antes era la variable X, ahora sera la posicion de memoria ocupada por la variable.

Es decir, que el usar variables es para darnos una gran comodidad a los programadores. Podriamos hacer un programa sin usar variables, indicando posiciones de memoria directamente, pero eso es ya mas parecido a codigo maquina puro que a ensamblador.

+ En el segundo caso, el de indicar la posicion de memoria concreta, hay que tener en cuenta si esa posicion de memoria la utilizamos como un byte o como una palabra. Esto es asi ya que si por medio del programa queremos guardar un 5 en la posicion de memoria 7654h (por ejemplo), el procesador no sabe si queremos guardar un byte o una palabra. Para que no surja ningun tipo de lios, el lenguaje ensamblador cuenta con ciertos convencionalismos para tratar estas transferencias a memoria. Cuando queremos introducir un byte en una posicion dada de memoria lo  hacemos con el siguiente formato: MOV BYTE PTR  DS:[7654H],5 BYTE PTR indica que vamos a acceder a una posicion de memoria de tipo BYTE. Cuando queremos introducir una palabra a partir de una posicion de memoria el formato queda como sigue: MOV WORD PTR DS:[7654H],5 WORD PTR indica que vamos a acceder a una posicion de memoria de tipo WORD.

Tened en cuenta tambien que cuando se quiere acceder a una posicion concreta de memoria sin pasar por una variable, se debe indicar entre corchetes, como en los ejemplos de arriba.

Pero eso no es todo, se debe indicar un segmento, para que el procesador sepa a que zona de 64 ks de la memoria pertenece la posicion dada entre los corchetes.

En este caso indicamos el segmento DS (segmento de datos), que es lo usual. Aunque tambien podriamos haber seleccionado el segmento ES(segmento extra de datos) para asi poder transferir algo fuera de nuestra zona de datos.

Observese la manera de indicar una direccion en direccion segmentada, no real. Primero se indica el segmento, luego dos puntos para separar, y luego entre corchetes el offset o desplazamiento dentro de ese segmento. 

Segmento:[desplazamiento]

DS:[2626h], ES:[FFFFh], etc.

+ En el tercer caso nos valemos de un registro indice o base, el cual contiene la direccion de la posicion de memoria que nos interesa, para acceder a dicha posicion de memoria.

Un ejemplo: MOV BYTE PTR [DI],5

Observese que aqui no es necesario indicar el segmento al que nos referimos. Se coge por defecto el segmento DS.

En definitiva, cuando accedemos a memoria a traves de registros indice o base, no es necesario indicar el segmento. Mientras que si lo hacemos en forma directa, indicando la posicion de memoria tal que [2635h], debemos indicar el segmento con el que vamos a tratar.

Veamos ahora como se codifica una instruccion en la que se hace acceso a memoria.

* MOV WORD PTR DS:[7654H],5 > Esta instrucci¢n introduce el valor 5 a partir de la posicion de memoria 7654h. Y digo a partir, ya que necesita dos posiciones de memoria para almacenarlo, ya que se trata de un valor inmediato de 16 bits (esto se determina al poner lo del WORD PTR).

Con lo cual, la palabra con valor 5, queda almacenada en dos posiciones de memoria, la indicada [7654h] y la contigua [7655h].

Si tenemos en cuenta lo que hemos comentado antes acerca de como el 8086 almacena las datos de tipo palabra en memoria, sabremos de antemano que la posicion [7654h] contendr el valor 05, y la posicion [7655h] contendra el valor 00.

Veamos como se codifica esta instruccion:

MOV WORD PTR [7654H],5 > C7 06 54 76 05 00

Vemos que esta instruccion ha ocupado el maximo posible (6 bytes).

De tal forma que los 4 campos de instruccion estan presentes.

Vamos a estudiarla detenidamente:

Lo primero que tenemos es el codigo de operacion: C7.

Este codigo indica una operacion MOV sobre una direccion concreta o desplazamiento, y con un valor numerico de tipo palabra.

El 3§ y 4§ byte juntos forman el desplazamiento (tener en cuenta lo del tema del orden inverso en los bytes), y los bytes 5§ y 6§ juntos forman el valor inemdiato a introducir (tener en cuenta de nuevo lo del orden inverso). Y nos queda el 2§ byte, que es el byte EA o de  direccionamiento. 

¨Que por que lo he dejado para el final?

je. Porque llevo 2 o 3 horas intentando descubrir el por que de que sea 06. No me cuadra por ningun sitio, ya que este 6 indica que no hay desplazamiento, cuando si lo hay.

A ver si para la proxima leccion, consigo descifrar el misterio.