CÓDIGO MÁQUINA Y ASSEMBLER
Lenguaje de máquina

Un lenguaje de máquina es aquel con el que trabaja el microprocesador; para reaccionar y efectuar la operación que se desea, necesita de una secuencia de señales eléctricas almacenadas como "unos" y "ceros" en las posiciones de la memoria. Una y solo una secuencia de señales concreta, realiza una determinada operación. Identificaremos a partir de ahora la existencia de señal con un "1" y la ausencia con un "0".

Microprocesador imaginario

El Spectrum trabaja con el microprocesador Z-80 cuyo funcionamiento se explicará en el capítulo 3 de este curso. El Z-80 es un microprocesador un tanto complejo, de forma que para introducirnos en el estudio del código máquina vamos a idear un microprocesador umaginario con un funcionamiento extremadamente simplificado.

Supongamos un microprocesador que tiene un registro de índice "I" y uno aritmético "A", a los que identifica como "01" y "10" respectivamente.

Un registro en un microprocesador es un campo interno modificable; denominamos campo a un lugar donde se almacenan datos; de esto forma, un registro es algo similar a una posición de memoria pero interno al microprocesador, su función es parecida a la de las variables en el BASIC.

También dispone del siguiente repertorio de instrucciones, cada una de las cuales tiene asignado un código de operación:

OPERACION
CODIGO
 
Cargar registro
001
Almacenar registro
010
Sumar en registro aritmético
011
Restar en registro aritmético
100
Saltar si contenido cero
101
Saltar si contenido no cero
110
Decrementar registro índice
111

Suponemos que el ordenador en el que está incorporado utiliza posiciones de memoria de 10 bits (el Spectrum las utiliza de 8). Nuestro microprocesador trabaja con un formato fijo para entender la secuencia de señales tal que:

-- Los tres primeros bits son el identificativo o código de la operación que se quiere realizar.

-- Los dos siguientes son el identificativo del registro sobre el que se opera.

-- Los cinco siguientes y últimos indican la posición de memoria, si procede, que va desde 00000 a 11111.

[Figura 1]

El formato de instrucción quedaría como se muestra en la FIGURA 1 y las instrucciones serían las siguientes:

CARGAR REGISTRO:

Definición: Carga el registro indicado con el contenido de la posición de memoria.

Formato:

[CARGAR REGISTRO]

ALMACENAR REGISTRO:

Definición: Almacena el contenido del registro indicado en la posición de memoria.

Formato:

[ALMACENA REGISTRO]

SUMAR EN REGISTRO ARITMÉTICO:

Definición: Suma en el registro aritmético el contenido de la posición de memoria que resulta de sumar la posición de memoria indicada en la instrucción con el contenido del registro índice si está indicado

Formato:

[SUMAR EN REGISTRO ARITMÉTICO]

RESTAR EN REGISTRO ARITMÉTICO:

Definición: Resta en el registro aritmético el contenido de la posición de memoria que resulta de sumar la posición de memoria indicada en la instrucción con el contenido del registro índice si está indicado

Formato:

[RESTAR EN REGISTRO ARITMÉTICO]

SALTAR POR CONTENIDO CERO:

Definición: Salta a la posición de memoria indicada si el valor del registro señalado es cero

Formato:

[SALTAR POR CONTENIDO CERO]

SALTAR POR CONTENIDO NO CERO:

Definición: Salta a la posición de memoria indicada si el valor del registro señalado es distinto de cero

Formato:

[SALTAR POR CONTENIDO NO CERO]

DECREMENTAR EL REGISTRO ÍNDICE:

Definición: Resta uno al valor del registro índice

Formato:

[DECREMENTAR EL REGISTRO ÍNDICE]

Definido ya este microprocesador con la única intención de hacer más comprensibles los conceptos que se pretenden adquirir vamos, siguiendo la misma línea, a dar solución a un supuesto problema.

Supuesto

Se quiere sumar el contenido de las diez posiciones de memoria a partir de la posición 10110. Si todos los valores son cero o el resultado es 11111, almacenaremos 11111 en la posición 00000; si no, ponemos el resultado en la posición 00001.

Para irnos acostumbrando a trabajar con métodos de programación empezaremos por hacer el organigrama. Es interesante intentar hacerlo en un papel aparte y luego comprobar resultados. No tiene porqué ser exactamente igual; cualquier organigrama es válido siempre que funcione. Un posible organigrama está representado en la FIGURA 2.

[Figura 2]

Codificación

Basándonos en los códigos definidos anteriormente, iremos definendo las posiciones de memoria.

Campos:

DIRECCION
CONTENIDO
00011
00100
0000011111
0000001001 (9 en binario)

El programa lo cargaremos a partir de la posición de memoria 01000.

Instrucciones:

DIRECCION
 
INSTRUCCION
 
COMENTARIOS
 
01000
 
001 01 00100
 
(Cargar el registro "I" con un 9)
01001
 
011 01 10110
 
(Suma contenido 10110 + reg. "I")
01010
 
111 00 00000
 
(Decrementa el registro "I")
01011
 
110 01 01001
 
(Seguir sumando si no van 9 pos.)
01100
 
101 10 10100
 
(Saltar si resultado=cero a 10100)
01101
 
100 00 00011
 
(Restar a la suma el valor 11111)
01110
 
101 10 10100
 
(Saltar si resultado=cero a 10100)
01111
 
011 00 00011
 
(Recuperar valor acumulado)
10000
 
010 10 00000
 
(Almacenar resultado en pos. 00000)
 
SALIDA
 
 
10100
 
001 01 00011
 
(Cargar en reg. "I" el valor 11111)
10101
 
010 01 00001
 
(Almacenar resultado en pos. 00001)
 
SALIDA
 

La memoria quedaría configurada de la siguiente manera:

Memoria:

 
...00
 
...01
 
...10
 
...11
000.. 0000000000 0000000000 0000000000 0000011111
001.. 0000001001 0000000000 0000000000 0000000000
010.. 0010100100 0110110110 1110000000 1100101001
011.. 1011010100 1000000011 1011010100 0110000011
100.. 0101000000 0000000000 0000000000 0000000000
101.. 0010100011 0100100001 0000000000 0000000000
110.. 0000000000 0000000000 0000000000 0000000000
111.. 0000000000 0000000000 0000000000 0000000000

Como se puede ver, esto supone un trabajo tremendo, y la facilidad de cometer errores es evidente. Siguiendo nuestro desarrollo de microprocesador simulado, tenemos que buscar una manera de facilitar el trabajo, para lo cual vamos a hacer corresponder a cada instrucción con una secuencia de letras que nos sirva para recordarla y nos de una idea de la operación que debe realizar. A esto se le denomina representación simbólica o código nemotécnico.

OPERACION
 
CODIGO
MAQUINA
 
NEMOTECNICO
 
Cargar registro
 
001
 
CA
Almacenar registro
 
010
 
AL
Sumar en registro aritmético
 
011
 
SUM
Restar en registro aritmético
 
100
 
RES
Saltar si contenido cero
 
101
 
SC
Saltar si contenido no-cero
 
110
 
SNC
Decrementar registro "I"
 
111
 
DEC

Se verá que es más fácil recordar el código nemotécnico o simbólico que los números de código máquina.

Sigamos simplificando, cuando nos refiramos a los registros en lugar de llamarlos 01 y 10 llamaremos al registro índice "I" y al registro aritmético "A".

Registro índice
I
código
01
Registro aritmético
A
código
10

Por último, cada vez que nos refiramos a una posición de memoria en lugar de recordar el valor numérico de su dirección, le daremos un nombre o literal, este literal tiene el valor de la dirección que representa y se le conoce con el nombre genérico de etiqueta.

Entonces diremos que la representación simbólica de una instrucción es la siguiente:

ETIQUETA
 
NEMOTECNICO/REGISTRO
 
POSICION DE MEMORIA
(opcional)
 
(opcional)
 
(opcional)

Codificación del supuesto en lenguaje simbólico:

Campos:

RESULTADO
 
(0)
OTROCASO
 
(1)
CONSTANTE
 
(31)
 (valor dec. de 11111)
NUEVE
 
(9)
NUMEROS
=
22
 (valor dec. de 10110)

Cuando ponemos el número entre paréntesis indicamos el contenido del campo, y cuando se pone el signo "=" nos referimos al valor que tiene el literal. Cualquier simbólico tiene que diferenciar entre dirección y contenido.

Instrucciones:

 
CA/I
 
NUEVE
SUMAR
 
SUM/I
 
NUMEROS
 
DEC
 
 
SNC/I
 
SUMAR
 
SC/A
 
NOSUMA
 
RES
 
CONSTANTE
 
SC/A
 
NOSUMA
 
SUM
 
CONSTANTE
 
AL/A
 
RESULTADO
FIN
NOSUMA
 
CA/I
 
CONSTANTE
 
AL/I
 
OTROCASO
FIN

¿Qué se ha hecho?

1º. Definir los campos.

Damos a unos campos un nombre y un contenido inicial, siempre que queramos tomar su contenido nos acordaremos sólo del nombre del campo.

2º. Definir constantes.

Damos a un literal un valor siempre que necesitemos ese valor sólo tendremos que recordar el nombre.

La constante NUMEROS nos indica el comienzo del campo donde tenemos los sumandos.

3º. Codificar la rutina.

Usando los códigos nemotécnicos construimos las instrucciones. Siempre que tengamos que saltar a una instrucción definiremos delante una etiqueta, así al tener que codificar la instrucción de salto con poner el literal no hay que andar considerando cual es la dirección a la que se quiere saltar.

Bien, ya tenemos un programa codificado en un lemguaje simbólico, lo que se llama un programa fuente. Está claro que para nosotros es más fácil entenderlo que la secuencia de números, pero la máquina no lo entiende. ¿Qué es lo que nos hacía falta?, sencillamente algo que lo convirtiera.

Intérpretes y ensambladores

Un intérprete sería un programa que fuera leyendo una a una todas las instrucciones, pasándolas al código máquina y dándoselas de esa manera al microprocesador para que las ejecute. Algo así como ocurre con el BASIC.

En un lenguaje ASSEMBLER esto no es rentable, lo que se usa son unos programas llamados ENSAMBLADORES o COMPILADORES que cogen las instrucciones, las colocan una detrás de otra en lenguaje máquina, calculan las direcciones relativas de cada campo o etiqueta y dan como resultado un programa en código máquina que se llama código objeto. Este programa posteriormente se carga en una posición de memoria de la máquina y ese cargador le suma a las direcciones relativas el valor de la dirección de carga con lo cual tenemos un programa listo para ejecutarse, a este programa se le llama absoluto. Todos los ensambladores que existen para el Spectrum, dan como resultado un programa absoluto.

En el supuesto que hemos realizado en una máquina imaginaria, el programa absoluto es la primera secuencia de números que hicimos.

Ejecución

El programa absoluto en código máquina lo ejecuta el microprocesador directamente según los siguientes pasos:

-- lee instrucción

-- incrementa puntero siguiente instrucción

-- ejecuta instrucción

Cuando hay una instrucción que modifica la secuencia del programa lo que hace es modificar el puntero de la siguiente instrucción (de forma equivalente a un GOTO en BASIC, pero en vez de mandar a un número de línea, manda a una posición de memoria apuntada por una etiqueta).

Como se ve, la ejecución de un programa absoluto no requiere la participación de ningún otro programa, como en el caso del BASIC que requiere la actuación del programa MONITOR, por lo cual es muchísimo más rápido.

Tanto el lenguaje de máquina como el simbólico hasta aquí visto es imaginario, sólo nos ha valido para la mayor comprensión del tema. Hemos ideado un microprocesador sumamente sencillo con el fin de que el lector comprendiera fácilmente lo que es un código máquina. A partir de ahora, nos ceñiremos al microprocesador Z-80 de ZILOG, su repertorio de instrucciones abarca más de 500, el formato de instrucción no es tan sencillo como el visto aquí y trabaja sobre posiciones de memoria de 8 bits; no obstante, los principios básicos de funcionamiento son los mismos.