Conceptos Basicos
- Información en las computadoras.
- Unidades de información
- Sistemas numéricos
- Convertir números binarios a decimales
- Convertir números decimales a binarios
- Sistema Hexadecimal
- Métodos de representación de datos en una computadora.
- Código ASCII
- Método BCD
- Representación de punto flotante
- Trabajando con el lenguaje ensamblador.
- Proceso de creación de un programa
- Registros de la UCP
- La estructura del ensamblador
- Nuestro primer programa
- Guardar y cargar los programas
- Condiciones, ciclos y bifurcaciones
- Interrupciones
Para que la PC pueda procesar la información es
necesario que ésta se encuentre en celdas especiales llamadas registros.
Los registros son conjuntos de 8 o 16 flip-flops
(basculadores o biestables).
Un flip-flop es un dispositivo capaz de almacenar
dos niveles de voltaje, uno bajo, regularmente de 0.5 volts y otro alto comunmente de 5
volts. El nivel bajo de energía en el flip-flop se interpreta como apagado o 0, y el
nivel alto como prendido o 1. A estos estados se les conoce usualmente como bits, que son
la unidad mas pequeña de información en una computadora.
A un grupo de 16 bits se le conoce
como palabra, una palabra puede ser dividida en grupos de 8 bits llamados bytes, y a los
grupos de 4 bits les llamamos nibbles.
El sistema numérico que utilizamos a diario es el
sistema decimal, pero este sistema no es conveniente para las máquinas debido a que la
información se maneja codificada en forma de bits prendidos o apagados; esta forma de
codificación nos lleva a la necesidad de conocer el cálculo posicional que nos permita
expresar un número en cualquier base que lo necesitemos.
Es posible representar un número determinado en
cualquier base mediante la siguiente formula:
Donde n es la posición del dígito
empezando de derecha a izquierda y numerando a partir de cero. D es el dígito sobre el
cual operamos y B es la base numérica empleada.
Trabajando en el lenguaje ensamblador nos
encontramos con la necesidad de convertir números del sistema binario, que es el empleado
por las computadoras, al sistema decimal utilizado por las personas.
El sistema binario está basado en unicamente dos
condiciones o estados, ya sea encendido (1) o apagado (0), por lo tanto su base es dos.
Para la conversión podemos utilizar la formula de
valor posicional:
Por ejemplo, si tenemos el numero binario 10011,
tomamos de derecha a izquierda cada dígito y lo multiplicamos por la base elevada a la
nueva posición que ocupan:
El caracter ^ es
utilizado en computación como símbolo de potenciación y el caracter *
se usa para representar la multiplicación.
Existen varios métodos de conversión de números
decimales a binarios; aquí solo se analizará uno. Naturalmente es mucho mas fácil una
conversión con una calculadora científica, pero no siempre se cuenta con ella, así que
es conveniente conocer por lo menos una forma manual para hacerlo.
El método que se explicará utiliza la división
sucesiva entre dos, guardando el residuo como dígito binario y el resultado como la
siguiente cantidad a dividir.
Tomemos como ejemplo el número 43 decimal.
43/2 = 21 y su residuo es 1
21/2 = 10 y su residuo es 1
10/2 = 5 y su residuo es 0
5/2 = 2 y su residuo es 1
2/2 = 1 y su residuo es 0
1/2 = 0 y su residuo es 1
Armando el número de abajo hacia
arriba tenemos que el resultado en binario es 101011
En la base hexadecimal tenemos 16 dígitos que van
del 0 al 9 y de la letra A hasta la F (estas letras representan los números del 10 al
15). Por lo tanto, contamos 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E y F.
La conversión entre numeración binaria y
hexadecimal es sencilla. Lo primero que se hace para una conversión de un número binario
a hexadecimal es dividirlo en grupos de 4 bits, empezando de derecha a izquierda. En caso
de que el último grupo (el que quede mas a la izquierda) sea menor de 4 bits se rellenan
los faltantes con ceros.
Tomando como ejemplo el número binario 101011 lo
dividimos en grupos de 4 bits y nos queda:
10; 1011
Rellenando con ceros el último grupo (el de la
izquierda):
0010; 1011
Después tomamos cada grupo como un número
independiente y consideramos su valor en decimal:
0010 = 2; 1011 = 11
Pero como no podemos representar este número
hexadecimal como 211 porque sería un error, tenemos que sustituir todos los valores
mayores a 9 por su respectiva representación en hexadecimal, con lo que obtenemos:
2BH (Donde la H representa la base hexadecimal)
Para convertir un número de
hexadecimal a binario solo es necesario invertir estos pasos: se toma el primer dígito
hexadecimal y se convierte a binario, y luego el segundo, y así sucesivamente hasta
completar el número.
ASCII generalmente se pronuncia "aski", es
un acrónimo de American Standard Code for Information Interchange.
Este código asigna a las letras del alfabeto, a los
dígitos decimales del 0 al 9 y a varios símbolos adicionales un número binario de 7
bits (poniéndose el bit 8 en su estado de apagado o 0).
De esta forma cada letra, dígito o caracter
especial ocupa un byte en la memoria de la computadora.
Podemos observar que este método de representación
de datos es muy ineficiente en el aspecto numérico, ya que en formato binario nos basta
un solo byte para representar numeros de 0 a 255, en cambio con el código ASCII un byte
puede representar unicamente un dígito.
Debido a esta ineficiencia, el
código ASCII es principalmente utilizado en la memoria para representar texto.
BCD es un acrónimo de Binary Coded Decimal.
En esta notación se utilizan grupos de 4 bits para
representar cada dígito decimal del 0 al 9. Con este método podemos representar dos
dígitos por byte de información.
An cuando este método es mucho mas práctico
para representación de números en la memoria en comparación al ASCII, todavía se queda
por debajo del binario, ya que con un byte en el método BCD solo podemos representar
dígitos del 0 al 99, en cambio, en formato binario podemos representar todos los dígitos
desde 0 hasta 255.
Este formato es utilizado
principalmente para representar números muy grandes en aplicaciones mercantiles ya que
facilita las operaciones con los mismos evitando errores de redondeo.
Esta representación esta basada en la notación
científica, esto es, representar un número en dos partes: su mantisa y su exponente.
Poniendo como ejemplo el número 1234000, podemos
representarlo como 1.123*10^6, en esta última notación el exponente nos indica el
número de espacios que hay que mover el espacio hacia la derecha para obtener el
resultado original.
En caso de que el exponente fuera
negativo nos estaría indicando el número de espacios que hay que recorrer el punto
decimal hacia la izquierda para obtener el original.
Para la creación de un programa es necesario seguir
cinco pasos: Diseño del algoritmo, codificación del mismo, su traducción a lenguaje
máquina, la prueba del programa y la depuración.
En la etapa de diseño se plantea el problema a
resolver y se propone la mejor solución, creando diagramas esquemáticos utilizados para
el mejor planteamiento de la solución.
La codificación del programa consiste en escribir
el programa en algún lenguaje de programación; en este caso específico en ensamblador,
tomando como base la solución propuesta en el paso anterior.
La traducción al lenguaje máquina es la creación
del programa objeto, esto es, el programa escrito como una secuencia de ceros y unos que
pueda ser interpretado por el procesador.
La prueba del programa consiste en verificar que el
programa funcione sin errores, o sea, que haga lo que tiene que hacer.
La última etapa es la eliminación de las fallas
detectadas en el programa durante la fase de prueba. La corrección de una falla
normalmente requiere la repetición de los pasos comenzando desde el primero o el segundo.
Para crear un programa en ensamblador existen dos
opciones, la primera es utilizar el MASM (Macro Assembler, de Microsoft), y la segunda es
utilizar el debugger, en esta primera sección utilizaremos este último ya que se
encuentra en cualquier PC con el sistema operativo MS-DOS, lo cual lo pone al alcance de
cualquier usuario que tenga acceso a una máquina con estas caracteristicas.
Debug solo puede crear archivos con
extensión .COM, y por las características de este tipo de programas no pueden ser
mayores de 64 kb, además deben comenzar en el desplazamiento, offset, o dirección de
memoria 0100H dentro del segmento específico.
La UCP tiene 14 registros internos, cada uno de 16
bits. Los primeros cuatro, AX, BX, CX, y DX son registros de uso general y tambien pueden
ser utilizados como registros de 8 bits, para utilizarlos como tales es necesario
referirse a ellos como por ejemplo: AH y AL, que son los bytes alto (high) y bajo (low)
del registro AX. Esta nomenclatura es aplicable también a los registros BX, CX y DX.
Los registros son conocidos por sus nombres
específicos:
-
- AX Acumulador
- BX Registro base
- CX Registro contador
- DX Registro de datos
- DS Registro del segmento de datos
- ES Registro del segmento extra
- SS Registro del segmento de pila
- CS Registro del segmento de código
- BP Registro de apuntadores base
- SI Registro índice fuente
- DI Registro índice destino
- SP Registro del apuntador de la pila
- IP Registro de apuntador de siguiente instrucción
- F Registro de banderas
Es posible visualizar los valores de los registros
internos de la UCP utilizando el programa Debug. Para empezar a trabajar con Debug digite
en el prompt de la computadora:
C:\> Debug [Enter]
En la siguiente linea aparecera un guión, éste es
el indicador del Debug, en este momento se pueden introducir las instrucciones del Debug.
Utilizando el comando:
- r [Enter]
Se desplegaran todos los contenidos de los registros
internos de la UCP; una forma alternativa de mostrarlos es usar el comando "r"
utilizando como parametro el nombre del registro cuyo valor se quiera visualizar. Por
ejemplo:
- rbx
Esta instrucción desplegará unicamente el
contenido del registro BX y cambia el indicador del Debug de " - "
a " : "
Estando así el prompt es posible cambiar el valor
del registro que se visualizó tecleando el nuevo valor y a continuación [Enter], o se
puede dejar el valor anterior presionando [Enter] sin telclear ningún valor.
Es posible cambiar el valor del registro de
banderas, así como utilizarlo como estructura de control en nuestros programas como se
verá mas adelante. Cada bit del registro tiene un nombre y significado especial, la lista
dada a continuación describe el valor de cada bit, tanto apagado como prendido y su
relación con las operaciones del procesador:
- Overflow
- NV = no hay desbordamiento;
- OV = sí lo hay
- Direction
- UP = hacia adelante;
- DN = hacia atras;
- Interrupts
- DI = desactivadas;
- EI = activadas
- Sign
- PL = positivo;
- NG = negativo
- Zero
- NZ = no es cero;
- ZR = sí lo es
- Auxiliary Carry
- NA = no hay acarreo auxiliar;
- AC = hay acarreo auxiliar
- Parity
- PO = paridad non;
- PE = paridad par;
- Carry
- NC = no hay acarreo;
- CY = Sí lo hay
En el lenguaje ensamblador las lineas de código
constan de dos partes, la primera es el nombre de la instrucción que se va a ejecutar y
la segunda son los parámetros del comando u operandos. Por ejemplo:
add ah bh
Aquí "add" es el comando a ejecutar (en
este caso una adición) y tanto "ah" como "bh" son los parámetros.
El nombre de las instrucciones en este lenguaje esta
formado por dos, tres o cuatro letras. a estas instrucciones tambien se les llama nombres
mnemónicos o códigos de operación, ya que representan alguna función que habrá de
realizar el procesador.
Existen algunos comandos que no requieren parametros
para su operación, as como otros que requieren solo un parámetro.
Algunas veces se utilizarán las instrucciones como
sigue:
add al,[170]
Los corchetes en el segundo
parámetro nos indican que vamos a trabajar con el contenido de la casilla de memoria
número 170 y no con el valor 170, a ésto se le conoce como direccionamiento directo.
Vamos a crear un programa que sirva para ilustrar lo
que hemos estado viendo, lo que haremos será una suma de dos valores que introduciremos
directamente en el programa:
El primer paso es iniciar el Debug, este paso
consiste unicamente en teclear debug [Enter] en el prompt del sistema
operativo.
Para ensamblar un programa en el Debug se utiliza el
comando "a" (assemble); cuando se utiliza este comando se le
puede dar como parametro la dirección donde se desea que se inicie el ensamblado, si se
omite el parametro el ensamblado se iniciará en la localidad especificada por CS:IP,
usualmente 0100H, que es la localidad donde deben iniciar los programas con extensión
.COM, y sera la localidad que utilizaremos debido a que debug solo puede crear este tipo
específico de programas.
Aunque en este momento no es necesario darle un
parametro al comando "a" es recomendable hacerlo para evitar problemas una vez
que se haga uso de los registros CS:IP, por lo tanto tecleamos:
- a0100 [Enter]
Al hacer ésto aparecerá en la pantalla algo como:
0C1B:0100 y el cursor se posiciona a la derecha de estos números, nótese que los
primeros cuatro dígitos (en sistema hexagesimal) pueden ser diferentes, pero los últimos
cuatro deben ser 0100, ya que es la dirección que indicamos como inicio. Ahora podemos
introducir las instrucciones:
- 0C1B:0100 mov ax,0002 ;coloca
el valor 0002 en el registro ax
- 0C1B:0103 mov bx,0004 ;coloca
el valor 0004 en el registro bx
- 0C1B:0106 add ax,bx ;le
adiciona al contenido de ax el contenido de bx
- 0C1B:0108 int 20 ; provoca la
terminación del programa.
- 0C1B:010A
No es necesario escribir los comentarios que van
despues del ";". Una vez digitado el último comando, int 20,
se le da [Enter] sin escribir nada mas, para volver al prompt del debuger.
La última linea escrita no es propiamente una
instrucción de ensamblador, es una llamada a una interrupción del sistema operativo,
estas interrupciones serán tratadas mas a fondo en un capítulo posterior, por el momento
solo es necesario saber que nos ahorran un gran número de lineas y son muy útiles para
accesar a funciones del sistema operativo.
Para ejecutar el programa que escribimos se utliza
el comando "g", al utilizarlo veremos que aparece un mensaje
que dice: "Program terminated normally". Naturalmente con un mensaje como éste
no podemos estar seguros que el programa haya hecho la suma, pero existe una forma
sencilla de verificarlo, utilizando el comando "r" del Debug podemos ver los
contenidos de todos los registros del procesador, simplemente teclee:
- r [Enter]
Aparecera en pantalla cada registro con su
respectivo valor actual:
- AX=0006BX=0004CX=0000DX=0000SP=FFEEBP=0000SI=0000DI=0000
- DS=0C1BES=0C1BSS=0C1BCS=0C1BIP=010A NV UP EI PL NZ NA
PO NC
- 0C1B:010A 0F DB oF
Existe la posibilidad de que los registros contengan
valores diferentes, pero AX y BX deben ser los mismos, ya que son los que acabamos de
modificar.
Otra forma de ver los valores, mientras se ejecuta
el programa es utilizando como parámetro para "g" la dirección donde queremos
que termine la ejecución y muestre los valores de los registros, en este caso sería: g108,
esta instrucción ejecuta el programa, se detiene en la dirección 108 y muestra los
contenidos de los registros.
También se puede llevar un seguimiento de lo que
pasa en los registros utilizando el comando "t" (trace), la
función de este comando es ejecutar linea por linea lo que se ensambló mostrando cada
vez los contenidos de los regitros.
Para salir del Debug se utiliza el
comando "q" (quit).
No sería práctico tener que digitar todo un
programa cada vez que se necesite, para evitar eso es posible guardar un programa en el
disco, con la enorme ventaja de que ya ensamblado no será necesario correr de nuevo debug
para ejecutarlo.
Los pasos a seguir para guardar un programa ya
almacenado en la memoria son:
- Obtener la longitud del programa restando la
dirección final de la dirección inicial, naturalmente en sistema hexadecimal.
- Darle un nombre al programa y extensión
- Poner la longitud del programa en el registro CX
- Ordenar a Debug que escriba el programa en el disco.
Utilizando como ejemplo el programa del capítulo
anterior tendremos una idea mas clara de como llevar estos pasos:
Al terminar de ensamblar el programa se vería así:
- 0C1B:0100 mov ax,0002
- 0C1B:0103 mov bx,0004
- 0C1B:0106 add ax,bx
- 0C1B:0108 int 20
- 0C1B:010A
- - h 10a 100
- 020a 000a
- - n prueba.com
- - rcx
- CX 0000
- :000a
- -w
- Writing 000A bytes
Para obtener la longitud de un programa se utiliza
el comando "h", el cual nos muestra la suma y resta de dos números en
hexadecimal. Para obtener la longitud del nuestro le proporcionamos como parámetros el
valor de la dirección final de nuestro programa (10A) y el valor de la dirección inicial
(100). El primer resultado que nos muestra el comando es la suma de los parámetros y el
segundo es la resta.
El comando "n" nos permite poner un nombre
al programa.
El comando "rcx" nos permite cambiar el
contenido del registro CX al valor que obtuvimos del tamaño del archivo con
"h", en este caso 000a, ya que nos interesa el resultado de la resta de la
dirección inicial a la dirección final.
Por último el comando w escribe nuestro programa en
el disco, indicandonos cuantos bytes escribió.
Para cargar un archivo ya guardado son necesarios
dos pasos:
- Proporcionar el nombre del archivo que se cargará.
- Cargarlo utilizando el comando "l" (load).
Para obtener el resultado correcto de los
siguientes pasos es necesario que previamente se haya creado el programa anterior.
Dentro del Debug escribimos lo siguiente:
- - n prueba.com
- - l
- - u 100 109
- 0C3D:0100 B80200 MOV AX,0002
- 0C3D:0103 BB0400 MOV BX,0004
- 0C3D:0106 01D8 ADD AX,BX
- 0C3D:0108 CD20 INT 20
El último comando, "u", se utiliza para
verificar que el programa se cargó en memoria, lo que hace es desensamblar el código y
mostrarlo ya desensamblado. Los parámetros le indican a Debug desde donde y hasta donde
desensamblar.
Debug siempre carga los programas en
memoria en la dirección 100H, a menos que se le indique alguna otra.
Estas estructuras, o formas de control le dan a la
máquina un cierto grado de desición basado en la información que recibe.
La forma mas sencilla de comprender este tema es por
medio de ejemplos.
Vamos a crear tres programas que hagan lo mismo:
desplegar un número determinado de veces una cadena de caracteres en la pantalla.
- - a100
- 0C1B:0100 jmp 125 ; brinca a la dirección
125H
- 0C1B:0102 [Enter]
- - e 102 'Cadena a visualizar 15 veces' 0d 0a
'$'
- - a125
- 0C1B:0125 MOV CX,000F ; veces que se
desplegara la cadena
- 0C1B:0128 MOV DX,0102 ; copia cadena al
registro DX
- 0C1B:012B MOV AH,09 ; copia valor 09 al
registro AH
- 0C1B:012D INT 21 ; despliega cadena
- 0C1B:012F LOOP 012D ; si CX>0 brinca a
012D
- 0C1B:0131 INT 20 ; termina el programa.
Por medio del comando "e" es posible
introducir una cadena de caracteres en una determinada localidad de memoria, dada como
parámetro, la cadena se introduce entre comillas, le sigue un espacio, luego el valor
hexadecimal del retorno de carro, un espacio, el valor de linea nueva y por último el
símbolo '$' que el ensamblador interpreta como final de la cadena. La interrupción 21
utiliza el valor almacenado en el registro AH para ejecutar una determinada función, en
este caso mostrar la cadena en pantalla, la cadena que muestra es la que está almacenada
en el registro DX. La instrucción LOOP decrementa automaticamente el registro CX en uno y
si no ha llegado el valor de este registro a cero brinca a la casilla indicada como
parámetro, lo cual crea un ciclo que se repite el número de veces especificado por el
valor de CX. La interrupción 20 termina la ejecución del programa.
Otra forma de realizar la misma función pero sin
utilizar el comando LOOP es la siguiente:
- - a100
- 0C1B:0100 jmp 125 ; brinca a la dirección
125H
- 0C1B:0102 [Enter]
- - e 102 'Cadena a visualizar 15 veces' 0d 0a
'$'
- - a125
- 0C1B:0125 MOV BX,000F ; veces que se
desplegara la cadena
- 0C1B:0128 MOV DX,0102 ; copia cadena al
registro DX
- 0C1B:012B MOV AH,09 ; copia valor 09 al
registro AH
- 0C1B:012D INT 21 ; despliega cadena
- 0C1B:012F DEC BX ; decrementa en 1 a BX
- 0C1B:0130 JNZ 012D ; si BX es diferente a 0
brinca a 012D
- 0C1B:0132 INT 20 ; termina el programa.
En este caso se utiliza el registro BX como contador
para el programa, y por medio de la instrucción "DEC" se disminuye su valor en
1. La instrucción "JNZ" verifica si el valor de B es diferente a 0, esto con
base en la bandera NZ, en caso afirmativo brinca hacia la dirección 012D. En caso
contrario continúa la ejecución normal del programa y por lo tanto se termina.
Una útima variante del programa es utilizando de
nuevo a CX como contador, pero en lugar de utilizar LOOP utilizaremos decrementos a CX y
comparación de CX a 0.
- - a100
- 0C1B:0100 jmp 125 ; brinca a la dirección
125H
- 0C1B:0102 [Enter]
- - e 102 'Cadena a visualizar 15 veces' 0d 0a
'$'
- - a125
- 0C1B:0125 MOV DX,0102 ; copia cadena al
registro DX
- 0C1B:0128 MOV CX,000F ; veces que se
desplegara la cadena
- 0C1B:012B MOV AH,09 ; copia valor 09 al
registro AH
- 0C1B:012D INT 21 ; despliega cadena
- 0C1B:012F DEC CX ; decrementa en 1 a CX
- 0C1B:0130 JCXZ 0134 ; si CX es igual a 0
brinca a 0134
- 0C1B:0132 JMP 012D ; brinca a la
direcci&oauten 012D
- 0C1B:0134 INT 20 ; termina el programa
En este ejemplo se usó la instrucción JCXZ para
controlar la condición de salto, el significado de tal función es: brinca si CX=0
El tipo de control a utilizar
dependerá de las necesidades de programación en determinado momento.
- Definición de interrupción:
- Una interrupción es una instrucción que detiene la
ejecución de un programa para permitir el uso de la UCP a un proceso prioritario. Una vez
concluido este último proceso se devuelve el control a la aplicación anterior.
Por ejemplo, cuando estamos trabajando con un
procesador de palabras y en ese momento llega un aviso de uno de los puertos de
comunicaciones, se detiene temporalmente la aplicación que estabamos utilizando para
permitir el uso del procesador al manejo de la información que está llegando en ese
momento. Una vez terminada la transferencia de información se reanudan las funciones
normales del procesador de palabras.
Las interrupciones ocurren muy seguido,
sencillamente la interrupción que actualiza la hora del día ocurre aproximadamente 18
veces por segundo. Para lograr administrar todas estas interrupciones, la computadora
cuenta con un espacio de memoria, llamado memoria baja, donde se almacenan las direcciones
de cierta localidad de memoria donde se encuentran un juego de instrucciones que la UCP
ejecutará para despues regresar a la aplicación en proceso.
En los programas anteriores hicimos uso de la
interrupcion número 20H para terminar la ejecución de nuestros programas, ahora
utilizaremos otra interrupción para mostrar información en pantalla:
Utilizando Debug tecleamos:
- - a100
- 2C1B:0100 JMP 011D
- 2C1B:0102 [ENTER]
- - E 102 'Hola, como estas.' 0D 0A '$'
- - A011D
- 2C1B:011D MOV DX,0102
- 2C1B:0120 MOV AH,09
- 2C1B:0122 INT 21
- 2C1B:0123 INT 20
En este programa la interrupción 21H manda al
monitor la cadena localizada en la dirección a la que apunta el registro DX.
El valor que se le da a AH determina cual de las
opciones de la interrupción 21H sera utilizada, ya que esta interrupción cuenta con
varias opciones.
El manejo directo de interrupciones es una de las
partes mas fuertes del lenguaje ensamblador, ya que con ellas es posible controlar
eficientemente todos los dispositivos internos y externos de una computadora gracias al
completo control que se tiene sobre operaciones de entrada y salida.
Menu principal
Siguiente Capitulo