Está en la página 1de 42

• Conceptos Básicos

Al desarrollarse las primeras computadoras electrónicas, se


vio la necesidad de programarlas, es decir, de almacenar en
memoria la información sobre la tarea que iban a ejecutar. Las
primeras se usaban como calculadoras simples; se les indicaban
los pasos de cálculo, uno por uno.
• Conceptos Básicos
John Von Neumann desarrolló el modelo que lleva su nombre,
para describir este concepto de "programa almacenado". En este
modelo, se tiene una abstracción de la memoria como un
conjunto de celdas, que almacenan simplemente números. Estos
números pueden representar dos cosas: los datos, sobre los que
va a trabajar el programa; o bien, el programa en sí.
• Conceptos Básicos
¿Cómo es que describimos un programa como números? Se tenía el
problema de representar las acciones que iba a realizar la computadora,
y que la memoria, al estar compuesta por switches correspondientes al
concepto de bit, solamente nos permitía almacenar números binarios.
La solución que se tomó fue la siguiente: a cada acción que sea capaz
de realizar nuestra computadora, asociarle un número, que será su

código de operación (opcode) .


• Conceptos Básicos
La descripción y uso de los opcodes es lo que llamamos lenguaje de
máquina . Es decir, la lista de códigos que la máquina va a interpretar
como instrucciones, describe las capacidades de programación que
tenemos de ella; es el lenguaje más primitivo, depende directamente del
hardware, y requiere del programador que conozca el funcionamiento de
la máquina al más bajo nivel.
Cuando abstraemos los opcodes y los sustituimos por una palabra que
sea una clave de su significado, a la cual comúnmente se le conoce
como mnemónico , tenemos el concepto de Lenguaje Ensamblador
• Conceptos Básicos
Lenguaje Ensamblador es la primera abstracción del Lenguaje de
Máquina , consistente en asociar a los opcodes palabras clave que
faciliten su uso por parte del programador.
Como se puede ver, el Lenguaje Ensamblador es directamente traducible
al Lenguaje de Máquina, y viceversa; simplemente, es una abstracción
que facilita su uso para los seres humanos. Por otro lado, la computadora
no entiende directamente al Lenguaje Ensamblador; es necesario
traducirle a Lenguaje de Máquina.
• Conceptos Básicos
Una característica que hay que resaltar, es que al depender estos lenguajes
del hardware, hay un distinto Lenguaje de Máquina (y, por consiguiente, un
distinto Lenguaje Ensamblador) para cada CPU. Por ejemplo, podemos
mencionar tres lenguajes completamente diferentes, que sin embargo
vienen de la aplicación de los conceptos anteriores:
1.Lenguaje Ensamblador de la familia Intel 80x86
2.Lenguaje Ensamblador de la familia Motorola 68000
3.Lenguaje Ensamblador del procesador POWER, usado en las IBM
RS/6000.

Tenemos 3 fabricantes distintos, compitiendo entre sí y cada uno aplicando


conceptos distintos en la manufactura de sus procesadores, su arquitectura
y programación; todos estos aspectos, influyen en que el lenguaje de
máquina y ensamblador cambie bastante.
• Ventajas y desventajas
Una vez que hemos visto la evolución de los lenguajes, cabe preguntarse:
¿En estos tiempos "modernos", para qué quiero el Lenguaje Ensamblador?
El proceso de evolución trajo consigo algunas desventajas, que ahora
veremos como las ventajas de usar el Lenguaje Ensamblador, respecto a
un lenguaje de alto nivel:
1. Velocidad
2. Eficiencia de tamaño
3. Flexibilidad
Por otro lado, al ser un lenguaje más primitivo, el Ensamblador tiene ciertas
desventajas respecto a los lenguajes de alto nivel:
1. Tiempo de programación
2. Programas fuente grandes
3. Peligro de afectar recursos inesperadamente
4. Falta de portabilidad
• Assembler y la arquitectura
El procesador 8086 original introdujo nuevos conceptos en la
arquitectura de los procesadores para microcomputadoras:

* Arquitectura en Pipeline
* Bus de datos de 16 bits.
* Bus de direcciones de 20 bits

Vamos a explorar sus principales componentes, y cómo se


implementaron estos adelantos tecnológicos.
• Arquitectura del procesador 8086
• Arquitectura en Pipeline
Los procesadores previos al 8086, estaban limitados en su desempeño por
la necesidad de realizar los dos pasos principales de ejecución del
procesador: Fetch/Execute, en forma secuencial. Es decir, no se puede
ejecutar una instrucción hasta que se traiga de memoria (Fetch); y no
podían traerse instrucciones de memoria mientras ejecutaba una
instrucción, pues el procesador estaba ocupado.

Resumiendo, un alto porcentaje del tiempo, el procesador estaba ocupado


haciendo Fetch, cuando su función debiera ser ejecutar las instrucciones.
La capacidad de ejecutar instrucciones sólo se ocupaba en un bajo
porcentaje.
• Arquitectura en Pipeline
Para solucionar esto, Intel desarrolló la arquitectura en pipeline del
Fetch/Execute, en la cual simplemente se divide la tarea en dos secciones:
una encargada del Fetch (BIU: Bus Interface Unit y otra del Execute (EU:
Execute Unit). De esta manera, existen circuitos separados para cada
función, los cuales trabajan en paralelo. Si bien el proceso aún es
secuencial, solamente al principio se requiere desperdiciar tiempo en el
Fetch. A partir de ahí, Fetch va adelante del Execute, y trae instrucciones al
procesador mientras este ejecuta las anteriores.
• BIU: Bus Interface Unit
El BIU es la parte del 8086 que se encarga de hacer el Fetch; es
decir, su función es estar continuamente accesando la memoria,
trayendo de ella las instrucciones para ser ejecutadas por el
procesador.
Contiene los siguientes elementos:
1. Los registros de segmentos
2. La cola de prefetch
3. El generador de direcciones físicas
4. El Bus C
• Los Registros de Segmentos
 Estos registros son localidades de 16 bits dedicados a las
funciones de acceso a memoria. Se decidió, como se
describirá más adelante al analizar el mecanismo de
segmentación, dividir el Megabyte de memoria al que puede
accesar un 8086, en 65536 partes (segmentos), iniciadas
cada 1 Mb
 / 65536 = 16 bytes. Así, podemos en general decir que
los registros de segmentos nos van a indicar qué
sección de memoria seleccionamos.
• Los Registros de Segmentos
Existen 4 registros de segmentos:
DS: Data Segment (Segmento de Datos)
Este registro selecciona una sección de 64 Kb. que se dedica generalmente a colocar
en ella nuestras variables, por lo cual toma su nombre: sección de memoria dedicada
a datos.

CS: Code Segment (Segmento de Código)


Este registro selecciona el área de 64 Kb. que generalmente dedicamos al
código. En este caso, el CPU (específicamente, el BIU), siempre toma las
instrucciones de esta región de memoria; por lo que cuando requerimos
más de 64 Kb. de código (instrucciones), este registro tendrá que moverse,
tomando distintos valores según recorremos distintas regiones de memoria.
• Los Registros de Segmentos
SS: Stack Segment (Segmento de Pila)
Este registro selecciona la región de 64 Kb. que va a contener la pila
del sistema. Como su nombre lo indica, tendremos una estructura de datos,
con política LIFO (Last In, First Out = El último elemento en entrar, es el
primero en salir), con instrucciones básicas PUSH y POP para su manejo.
Esta estructura es usada por los programas de aplicación, pero también por
el procesador para el control de instrucciones que lo requieren, tales como
las llamadas a subrutinas yla atención de interrupciones.
• Los Registros de Segmentos

ES: Extra Segment (Segmento Extra)

Este registro nos permite seleccionar una sección de 64 Kb., que no está
destinada a ningún uso específico; por lo que el programador puede
aplicarla como comodín, generalmente como un segundo segmento de
datos; o bien, para el acceso a regiones de memoria del sistema, tales
como la memoria de video o las variables del BIOS
• Cola de Prefetch
Ya que el BIU trabaja en paralelo con el EU, requieren de un
mecanismo para comunicarse. Esto se logra con una cola,
estructura de datos que permite que el EU obtenga instrucciones
para ejecutar en el mismo orden en que el BIU las colocó en la
misma. Así, el EU puede solicitar instrucciones en cuanto esté listo
para ejecutar la siguiente, sin importar en qué paso está el BIU en la
obtención de instrucciones; y viceversa, el BIU puede continuar su
búsqueda de instrucciones, depositando la que acaba de obtener en
la cola, sin importar si el EU en este momento está ocupado
ejecutando.
• Generador de Direcciones Fisicas
Como las direcciones son de 20 bits, y los registros que nos permiten
accesarlas son de 16, se requiere de 2 registros para que en combinación
formen la dirección.
Esta combinación está basada en la arquitectura segmentada que
veremos más adelante. La función del generador de direcciones físicas
consiste en realizar esa combinación, para lo cual contiene la circuitería
necesaria para calcular la fórmula:

Dirección Fisica = Segmento * 0010h + Desplazamiento


• Bus C
Este bus interno del procesador permite que la información fluya entre la
interfaz con la memoria y los distintos elementos del BIU. Se requiere un
bus independiente, para que la información que usa el EU no se interfiera
con la que está procesando el BIU.
• EU: Execution Unit
El EU es la parte del 8086 que
Contiene los siguientes elementos:
1. Unidad de control
2. Registros de propósito general
3. Registros de índice
4. ALU
5. Registro de Banderas
6. Bus A
• Registros de Propósito general
Son 4 registros de 16 bits, que el programador usará para una diversidad de
funciones. Tienen varias características en común:
* Pueden ejecutar la mayoría de las operaciones del procesador,
incluyendo la generalidad de las instrucciones aritméticas y lógicas, salvo la
multiplicación y división.
* Se dividen lógicamente en 2 registros de 8 bits cada uno, llamados
parte alta y parte baja; refiriéndose el registro de parte baja a los bits 0-7
(menos significativos) y la parte alta a los bits 8-15 (más significativos). Se
conocen como el registro L (parte baja) y H (parte baja), precedidos por la
letra que identifica al registro. Por ejemplo, AX se divide en AH y AL.
* Pueden conectarse a los registros de segmentos; de hecho, siempre se
hará acceso a los registros de segmento a través de un registro de
propósito general, o de la pila.
• Registros de Propósito general
Los 4 registros tienen su nombre propio y ciertas especializaciones:
AX: Registro de acumulador
Este registro es el más usado, siendo de propósito general; y tiene ciertas
especializaciones, de ahí su nombre:
* Es el único que puede ser usado como multiplicando en la multiplicación
* Es el único que puede ser usado como dividendo en la división
* Es el parámetro que selecciona los distintos servicios del Sistema
Operativo, usados mediante el mecanismo de Interrupciones.
BX: Registro de Base
Este registro, además de las características generales de los registros de
propósito general, se especializa en el acceso a memoria en combinación
con los registros de índice, especialmente para el manejo de arreglos.
• Registros de Propósito general
CX: Registro de Contador
Este registro, como su nombre indica, será ampliamente usado como
contador. Puede ser usado como cualquier registro de propósito general,
pero tiene capacidades especiales para ser usado en instrucciones:
* de manejo de ciclos
* como contador en los corrimientos
* de manejo de memoria y strings
DX: Registro de Datos
Este registro de propósito general tiene como especializaciones:
* Formar con AX números de 32 bits, siendo en este caso la parte más
significativa.
* El acceso a puertos. Las instrucciones IN y OUT, que permiten dicho
acceso, requerirán en ciertos casos que el número de puerto esté en DX.
• Registros de Índice
Estos registros, de 16 bits, son muy similares a los de propósito general,
pero no tienen todas las capacidades de los mismos; en particular:

* No pueden conectarse directamente con los registros de segmentos.


* No pueden verse como dos registros de 8 bits (parte alta y parte baja).

Ahora bien, tienen sus propias habilidades. Específicamente, están


diseñados para combinarse con los registros de segmento, ser usados
como desplazamientos y así intervenir en el acceso a memoria. Se les usa,
por ello, para el acceso a arreglos o como apuntadores.
• Registros de Índice
Los 4 registros que se consideran en este grupo son:
SI: Source Index, o Apuntador a la Fuente
Este registro se especializa en el acceso a bytes o words dentro del
segmento de datos; existen instrucciones que lo toman por defecto como el
registro que apunta a una localidad de memoria que se va a leer, por lo cual
toma su nombre.
DI: Destination Index, Apuntador al Destino
Este registro se especializa en el acceso a bytes o words dentro del
segmento de datos o el extra; existen instrucciones que lo toman por
defecto como el registro que en combinación con el ES apunta a una
localidad de memoria que se va a escribir, por lo cual toma su nombre.
• Registros de Índice
BP: Base Pointer, o apuntador a la Base de la Pila.
Este registro es usado para accesar, dentro del segmento de pila, la
información. Principalmente nos permite determinar cuando hay un underflow, es
decir, cuando se quiere sacar más información de la pila que la que se ha colocado

en ella; y el acceso a variables locales y parámetros.

SP: Stack Pointer, o apuntador al tope de la Pila.


Este registro también está siempre asociado al manejo de la pila. Su función
especial es marcar el tope de la pila, y por tanto, indica en qué localidad de memoria
se localiza la información de la pila; se incrementa cuando se hace un POP, con lo
que apunta al siguiente elemento en la pila; y se decrementa al hacer un PUSH, con
lo que controla el acceso a la memoria de la pila.
• Registros de Índice
Este es un registro de 16 bits, usados como banderas. Esto quiere decir que cada
uno de los bits señala un evento dentro del procesador; cuando el bit tiene un valor
de 0, el evento no ocurrió (falso); y cuando tiene un valor de 1, el evento ocurrió
(verdadero). Entre las banderas más importantes mencionaremos:

ZF: Bandera de resultado 0


Se prende cuando el resultado de la última operación realizada en el ALU fue un 0.

CF: Bandera de carry


Se prende cuando el resultado de la última operación realizada en el ALU
(necesariamente, una suma o resta) generó un carry o un borrow.
• Registros de Índice
OF: Bandera de overflow
Se prende cuando el resultado de la última operación realizada en el ALU excedió la
capacidad del registro donde se almacena (overflow).

SF: Bandera de signo


Se prende cuando el resultado de la última operación realizada en el ALU fue
negativo, según la representación de complemento a 2 que mencionamos en clases
anteriores.

AF: Bandera de carry auxiliar


Se prende cuando el resultado de la última operación realizada en el ALU generó un
carry cuando estaba a la mitad.
• Registros de Índice
DF: Bandera de dirección
A diferencia de las anteriores, esta bandera no se prende como resultado del ALU,
sino que el programador la usa para controlar la dirección del acceso mediante
apuntadores, especialmente en las instrucciones de strings.

IF: Bandera de interrupción


Se prende cuando el procesador es interrumpido, y está ejecutando una rutina de
atención de interrupción. Esto evita que el procesador acepte interrupciones mientras
está procesando interrupciones previas, de manera que no se pierda el control del
procesador.
• Manejo de Memoria: Segmentación
Cuando Intel extendió el bus de direcciones, de 16 a 20 bits, se encontró
con el problema de cómo asociar los registros con las direcciones de
memoria. Para lograrlo, aplicó un concepto ya usado antes en mainframes,
conocido como segmentación; en este modelo, se usan dos registros para
seleccionar una localidad de memoria:
* Registro de Segmento: indica en qué segmento (región o sección) de
la memoria se encontrará la localidad deseada.
* Registro de Desplazamiento: indica en qué desplazamiento, es decir,
dirección relativa respecto al inicio de la sección, se encontrará la localidad
deseada.
• Manejo de Memoria:
Segmentación
 En el caso del 8086, los registros son de 16 bits, con lo
que un registro de segmento puede tomar 65536 valores
distintos; además, un registro de desplazamiento puede
tomar los mismos valores. De ahí las siguientes
propiedades:

 Un segmento inicia cada 16 bytes de memoria; de esta


manera, a cada sección de 16 bytes de la memoria se le
llama "párrafo".

 Dos segmentos de memoria llegan a tener sobreposición;


de manera que una misma localidad de memoria puede
• Mapa de Memoria RAM
0 0000:0000 - 0000:FFFF RAM Usuario
1 1000:0000 - 1000:FFFF RAM Usuario
2 2000:0000 - 2000:FFFF RAM Usuario
3 3000:0000 - 3000:FFFF RAM Usuario
4 4000:0000 - 4000:FFFF RAM Usuario
5 5000:0000 - 5000:FFFF RAM Usuario
6 6000:0000 - 6000:FFFF RAM Usuario
7 7000:0000 - 7000:FFFF RAM Usuario
8 8000:0000 - 8000:FFFF RAM Usuario
9 9000:0000 - 9000:FFFF RAM Usuario
A A000:0000 - A000:FFFF RAM Video
B B000:0000 - B000:FFFF RAM Video
C C000:0000 - C000:FFFF BIOS extra
D D000:0000 - D000:FFFF Libre ROM
E E000:0000 - E000:FFFF Libre ROM
F F000:0000 - F000:FFFF ROM BIOS
• Grupos de instrucciones
Instrucción Operandos Función equivalente en "C"
MOV destino, fuente destino = fuente
PUSH fuente [SS:SP] = fuente; SP += sizeof(destino)
POP destino SP -= sizeof(destino) ; destino = [SS:SP]
XCHG op1, op2 temp = op1; op1 = op2; op2 = temp

MOV acepta distintos modos de direccionamiento; las principales limitantes que tiene
son:
-Destino y fuente no pueden ser, ambos, referencias a memoria; al menos uno
de ellos debe ser un registro o una constante.
-El destino no puede ser una constante, debe ser un registro o una referencia a
memoria.
-Los registros de segmento solamente pueden usarse junto con un registro de
uso general.
-Destino y fuente deben tener el mismo "tipo" (ambos referencias a bytes, o a
word, pero no uno a byte y otro a word).
PUSH y POP requieren operandos de tamano Word. En 8086/88 tiene que ser un
registro o una localidad de memoria; en 80286 y superiores, la mejora principal es
que PUSH ya se puede hacer sobre una constante.
XCHG tiene las mismas características que MOV, con la única excepción de que no
acepta constantes de ninguno de los dos lados.
• Instrucciones Aritméticas
Instrucción Operandos Función equivalente en "C" Resultados
ADD op1, op2 op1 = op1 + op2
SUB op1, op2 op1 = op1 - op2
ADC op1, op2 op1 = op1 + op2 + Carry
SBB op1, op2 op1 = op1 - op2 - Carry
INC op1 op1 ++;
DEC op1 op1 --;
MUL op1 Si op1 es tipo byte: AX = AL * op1
Si op1 es tipo word: (DX:AX) = AX * op1
DIV op1 Si op1 es tipo byte: AL = AX / op1
AH = AX % op1
Si op1 es tipo word: AX = (DX : AX) / op1
DX = (DX : AX) % op1
NEG op1 op1 = - op1
• Manejo de Bits (Rotaciones y Desplazamientos)
Instrucción Operandos Función equivalente en "C"
SHL op1, cuenta op1 <<= cuenta
SHR op1, cuenta op1 >>= cuenta
SAR op1, cuenta (no hay equivalente)
ROL op1, cuenta (no hay equivalente)
ROR op1, cuenta (no hay equivalente)
En todas las instrucciones de rotación/desplazamiento de bits, cuenta puede
ser 1 o bien CX.

• Operaciones Lógicas (Booleanas)


Instrucción Operandos Función equivalente en "C"
AND op1, op2 op1 = op1 & op2
OR op1, op2 op1 = op1 | op2
XOR op1, op2 op1 = op1 ^ op2
NOT op1 op1 = ~op1.
• Evaluación de condiciones y saltos
Instrucción Operandos Función equivalente en "C"
JMP etiqueta goto <etiqueta>
CMP op1, op2 (no hay equivalente)

• Uso de procedimientos/subrutinas
Instrucción Operandos Función equivalente en "C"
CALL etiqueta Llamada a función
PUSH IP
PUSH CS ; Sólo si es llamada larga (CALL FAR)
JMP etiqueta
RET (ninguno) return
POP IP-temp
POP CS-temp ; Sólo si es llamada larga (RETF)
JMP CS-temp:IP-temp
• Principales Directivas (TASM)
¿Qué es una directiva?

Cuando un mnemónico o palabra reservada de ensamblador se va a


traducir a lenguaje máquina (código de operación del procesador), para su
posterior ejecución por parte del CPU, se le llama instrucción.

Pero también existen mnemónicos o palabras reservadas del Lenguaje


Ensamblador, que no se convierten a lenguaje de máquina, es decir, no son
instrucciones propias del lenguaje de máquina del procesador. Sin
embargo, son necesarias para controlar el proceso de Ensamblado; le dicen
al Ensamblador cuáles son las secciones del programa, cómo definir las
variables, y cómo estructurar el programa, entre otras aplicaciones. A estas
palabras, que el Ensamblador reconocerá pero no son instrucciones, les
llamamos "Directivas".
• Principales Directivas (TASM)
De segmentos (simplificadas):

Se les considera simplificadas, pues antiguamente se usaban directivas


mucho más complejas para realizar la misma función. Aún se requieren
esas directivas, pero sólo en los casos donde se accesarán múltiples
segmentos; inclusive en Turbo Assembler, solamente cuando hay más de
dos segmentos de datos. Por ello, no las veremos en el curso, pues es muy
rara su aplicación, y estas son más sencillas de entender y usar.
• Principales Directivas (TASM)
De segmentos (simplificadas):
.model <modelo de memoria>
Nos indica qué modelo de memoria se usará para ensamblar este programa.
Para las aplicaciones comunes, usaremos siempre el modelo small. De esta manera,
un programa fuente iniciará con la directiva ".model small"

.stack <tamaño en bytes>


Le indica al ensamblador cuánto espacio deberá reservar para la pila del sistema.
Como hay funciones básicas que usan la pila, aunque nosotros no la usemos
explícitamente, debe dejarse un espacio razonable. Para la generalidad de las
aplicaciones, basta reservar unos 256 bytes (100h). Por ello, generalmente veremos
a los programas fuentes tener la directiva ".stack 100h"
• Principales Directivas (TASM)
De segmentos (simplificadas):
.data
Esta directiva indica dónde inicia la definición de las variables.
Generalmente, esta zona de memoria se asociará al registro DS; de hecho,
se considera que es la definición del contenido y espacio reservado para el
segmento de datos por defecto.

.code
Esta directiva indicará al ensamblador dónde inicia el código. A partir de
ella, se encontrarán las instrucciones propiamente dichas.
• Principales Directivas (TASM)
Para uso de procedimientos:
<etiqueta> PROC
Asocia una etiqueta a un procedimiento. Por ejemplo, si ponemos:
Principal PROC esto definirá el punto de inicio del procedimiento
llamado "Principal".
<etiqueta> ENDP
Cierra un procedimiento, debe estar balanceado con un PROC para
abrirlo.
END {etiqueta}
Cierra un módulo de programa. Esta directiva le indica al Ensamblador
que ignore cualquier cosa que venga después; es como decirle que ahí
acabó el archivo con el código fuente.
El parámetro (opcional) "etiqueta" indica en qué procedimiento iniciará
la ejecución del programa. En caso de omitirse el parámetro opcional,
la ejecución se inicia en la primera instrucción después de la directiva
.code.
• Principales Directivas (TASM)
Definición de constantes
<identificador> EQU <expresión>
Define una constante llamada como el identificador, en una forma similar al
#define de C. Es decir, cada vez que se encuentre el identificador en el
programa fuente, se sustituirá (como texto) por la expresión indicada en el
EQU.
<identificador> = <expresión>
Es similar al EQU, solamente que en este caso podrá variarse (redefinirse)
la sustitución que corresponde al identificador.

También podría gustarte