5.1.1. Ciclo de ejecución.
Un ciclo de instrucción es el período que tarda la unidad central de proceso (CPU) en ejecutar una instrucción de lenguaje máquina.
Comprende una secuencia de acciones determinada que debe llevar a cabo la CPU para ejecutar cada instrucción en un programa. Cada instrucción del juego de instrucciones de una CPU, puede requerir diferente número de ciclos de instrucción para su ejecución. Un ciclo de instrucción está formado por uno o más ciclos máquina.
Para que cualquier sistema de proceso de datos basado en microprocesador (por ejemplo un ordenador) o microcontrolador (por ejemplo un reproductor de MP3) realice una tarea (programa) primero se debe buscar cada instrucción en la memoria principal y luego ejecutarla.
5.1.2. Arquitectura del juego de instrucciones
5.1.3. Representación del juego de instrucciones.
El juego de instrucciones de una arquitectura se representa desde dos puntos de vista:
1) Desde el punto de vista del computador, cada instrucción se representa como una secuencia de bits que se divide en campos, en los que cada campo corresponde a uno de los elementos que forman la instrucción. Cada bit es la representación de una señal eléctrica alta o baja. Esta manera de representar el juego de instrucciones se suele denominar código de máquina o lenguaje de máquina.
2) Desde el punto de vista del programador, las instrucciones se representan mediante símbolos y se utilizan expresiones mnemotécnicas o abreviaturas. Hay que tener presente que es muy difícil trabajar con las representaciones binarias propias del código de máquina. Esta manera de representar el juego de instrucciones se suele denominar código de ensamblador o lenguaje de ensamblador.
5.1.4. Formato de las instrucciones.
5.1.4.1. Elementos que componen una instrucción.
Los elementos que componen una instrucción, independientemente del tipo de arquitectura, son los siguientes:
- Código de operación: especifica la operación que hace la instrucción.
- Operando fuente: para hacer la operación pueden ser necesarios uno o más operandos fuente; uno o más de estos operandos pueden ser implícitos.
- Operando destino: almacena el resultado de la operación realizada. Puede estar explícito o implícito. Uno de los operandos fuente se puede utilizar también como operando destino.
- Dirección de la instrucción siguiente: especifica dónde está la instrucción siguiente que se debe ejecutar; suele ser una información implícita, ya que el procesador va a buscar automáticamente la instrucción que se encuentra a continuación de la última instrucción ejecutada. Solo las instrucciones de ruptura de secuencia especifican una dirección alternativa.
5.1.4.2. Tamaño de las instrucciones.
Uno de los aspectos más importantes a la hora de diseñar el formato de las instrucciones es determinar su tamaño. En este sentido, encontramos dos alternativas:
- Instrucciones de tamaño fijo: todas las instrucciones ocuparán el mismo número de bits. Esta alternativa simplifica el diseño del procesador y la ejecución de las instrucciones puede ser más rápida.
- Instrucciones de tamaño variable: el tamaño de las instrucciones dependerá del número de bits necesario para cada una. Esta alternativa permite diseñar un conjunto amplio de códigos de operación, el direccionamiento puede ser más flexible y permite poner referencias a registros y memoria. Como contrapartida, aumenta la complejidad del procesador.
Para determinar el tamaño de los campos de las instrucciones utilizaremos:
1) Código de operación. La técnica más habitual es asignar un número fijo de bits de la instrucción para el código de operación y reservar el resto de los bits para codificar los operandos y los modos de direccionamiento.
Ejemplo:
Si se destinan N bits para el código, tendremos disponibles 2N códigos de operación diferentes; es decir, 2N operaciones diferentes.
En instrucciones de tamaño fijo, cuantos más bits se destinen al código de operación, menos quedarán para los modos de direccionamiento y la representación de los operandos.
Se puede ampliar el número de instrucciones diferentes en el juego de instrucciones sin añadir más bits al campo del código de operación. Solo hay que añadir un campo nuevo, que llamaremos expansión del código de operación. Evidentemente, este campo solo se añade a las instrucciones en las que se haga ampliación de código.
Si de los 2N códigos de los que disponemos reservamos x para hacer la expansión de código y el campo de expansión es de k bits, tendremos 2k instrucciones adicionales por cada código reservado. De esta manera, en lugar de tener un total de 2N instrucciones, tendremos (2n – x) + x · 2k.
Ejemplo:
Tenemos n = 3, x = 4 y k = 2. En lugar de disponer de 23 = 8 instrucciones diferentes, tendremos (23 – 4) + 4 · 22 = 20.
Para hacer la expansión del código de operación debemos elegir 4 códigos. En este caso hemos elegido los códigos (010, 011, 100, 101).
5.1.5. Operandos.
5.1.5.1. Número de operandos.
Las instrucciones pueden utilizar un número diferente de operandos según el tipo de instrucción del que se trate.
Ejemplo:
Hay que tener presente que una misma instrucción en máquinas diferentes puede utilizar un número diferente de operandos según el tipo de arquitectura del juego de instrucciones que utilice la máquina.
La instrucción aritmética de suma (C = A + B) utiliza dos operandos fuente (A y B) y produce un resultado que se almacena en un operando destino (C):
En una arquitectura basada en pila, los dos operandos fuente se encontrarán en la cima de la pila y el resultado se almacenará también en la pila.
En una arquitectura basada en acumulador, uno de los operandos fuente se encontrará en el registro acumulador, el otro estará explícito en la instrucción y el resultado se almacenará en el acumulador.
En una arquitectura basada en registros de propósito general, los dos operandos fuente estarán explícitos. El operando destino podrá ser uno de los operandos fuente (instrucciones de dos operandos) o un operando diferente (instrucciones de tres operandos).
Según la arquitectura y el número de operandos de la instrucción podemos tener diferentes versiones de la instrucción de suma, tal como se ve en la tabla siguiente.
Hemos supuesto que, en las instrucciones de dos operandos, el primer operando actúa como operando fuente y también como operando destino. Los valores entre corchetes expresan una dirección de memoria.
5.1.5.2. Localización de los operandos.
Los operandos representan los datos que hemos de utilizar para ejecutar una instrucción. Estos datos se pueden encontrar en lugares diferentes dentro del computador. Según la localización podemos clasificar los operandos de la siguiente manera:
- Inmediato. El dato está representado en la instrucción misma. Podemos considerar que se encuentra en un registro, el registro IR (registro de instrucción), y está directamente disponible para el procesador.
- Registro. El dato estará directamente disponible en un registro dentro del procesador.
- Memoria. El procesador deberá iniciar un ciclo de lectura/escritura a memoria.
En el caso de operaciones de E/S, habrá que solicitar el dato al módulo de E/S adecuado y para acceder a los registros del módulo de E/S según el mapa de E/S que tengamos definido.
Según el lugar donde esté el dato, el procesador deberá hacer tareas diferentes con el fin de obtenerlo: puede ser necesario hacer cálculos y accesos a memoria indicados por el modo de direccionamiento que utiliza cada operando.
5.1.5.3. Tipo y tamaño de los operandos.
Los operandos de las instrucciones sirven para expresar el lugar donde están los datos que hemos de utilizar. Estos datos se almacenan como una secuencia de bits y, según la interpretación de los valores almacenados, podemos tener tipos de datos diferentes que, generalmente, también nos determinarán el tamaño de los operandos. En muchos juegos de instrucciones, el tipo de dato que utiliza una instrucción viene determinado por el código de operación de la instrucción.
Hemos de tener presente que un mismo dato puede ser tratado como un valor lógico, como un valor numérico o como un carácter, según la operación que se haga con él, lo que no sucede con los lenguajes de alto nivel.
A continuación presentamos los tipos generales de datos más habituales:
1) Dirección. Tipo de dato que expresa una dirección de memoria. Para operar con este tipo de dato, puede ser necesario efectuar cálculos y accesos a memoria para obtener la dirección efectiva
La manera de expresar y codificar las direcciones dependerá de la manera de acceder a la memoria del computador y de los modos de direccionamiento que soporte el juego de instrucciones.
2) Número. Tipo de dato que expresa un valor numérico. Habitualmente distinguimos tres tipos de datos numéricos (y cada uno de estos tipos se puede considerar con signo o sin signo):
a) Números enteros.
b) Números en punto fijo.
c) Números en punto flotante.
Como disponemos de un espacio limitado para expresar valores numéricos, tanto la magnitud de los números como su precisión también lo serán, lo que limitará el rango de valores que podemos representar.
Según el juego de instrucciones con el que se trabaja, en el código ensamblador, se pueden expresar los valores numéricos de maneras diferentes: en decimal, hexadecimal, binario, utilizando puntos o comas (si está en punto fijo) y con otras sintaxis para números en punto flotante; pero en código de máquina siempre los codificaremos en binario utilizando un tipo de representación diferente para cada tipo de valor. Cabe señalar, sin embargo, que uno de los más utilizados es el conocido como complemento a 2, que es una representación en punto fijo de valores con signo.
Ejemplo:
Si tenemos 8 bits y el dato es un entero sin signo, el rango de representación será [0,255], y si el dato es un entero con signo, utilizando complemento a 2 el rango de representación será [-128,127].
5.1.6. Tipos de instrucciones.
En general, los códigos de operación de un juego de instrucciones varían de una máquina a otra, pero podemos encontrar los mismos tipos de instrucciones en casi todas las arquitecturas y los podemos clasificar de la manera siguiente:
- Transferencia de datos
- Aritméticas
- Lógicas
- Transferencia del control
- Entrada/salida
5.1.6.1. Bits de resultado.
Los bits de resultado nos dan información de cómo es el resultado obtenido en una operación realizada en la unidad aritmeticológica (ALU) del procesador. Al ejecutar una instrucción aritmética o lógica, la unidad aritmeticológica hace la operación y obtiene un resultado; según el resultado, activa los bits de resultado que corresponda y se almacenan en el registro de estado para poder ser utilizados por otras instrucciones. Los bits de resultado más habituales son los siguientes:
- Bit de cero (Z): se activa si el resultado obtenido es 0.
- Bit de transporte (C): también denominado carry en la suma y borrow en la resta. Se activa si en el último bit que operamos en una operación aritmética se produce transporte, también se puede deber a una operación de desplazamiento. Se activa si al final de la operación nos llevamos una según el algoritmo de suma y resta tradicional operando en binario o si el último bit que desplazamos se copia sobre el bit de transporte y este es 1.
Cuando operamos con números enteros sin signo, el bit de transporte es equivalente al bit de desbordamiento; pero con número con signo (como es el caso del Ca2), el bit de transporte y el bit de desbordamiento no son equivalentes y la información que aporta el bit de transporte sobre el resultado no es relevante.
Bit de transporte en la resta
El bit de transporte, también denominado borrow, se genera según el algoritmo convencional de la resta. Pero si para hacer la resta A – B, lo hacemos sumando el complementario del sustraendo, A + (–B), el bit de transporte de la resta (borrow) será el bit de transporte (carry) negado obtenido de hacer la suma con el complementario (borrow = carry negado), salvo los casos en los que B = 0, donde tanto el carry como el borrow son iguales y valen 0.
- Bit de desbordamiento (V): también denominado overflow. Se activa si la última operación ha producido desbordamiento según el rango de representación utilizado. Para representar el resultado obtenido, en el formato que estamos utilizando, necesitaríamos más bits de los disponibles.
- Bit de signo (S): activo si el resultado obtenido es negativo, el bit más significativo del resultado es 1.
5.1.6.2. Instrucciones de transferencia de datos.
Las instrucciones de transferencia de datos transfieren un dato de una localización a otra dentro del computador.
El tipo y el tamaño de los datos pueden estar especificados de manera implícita en el código de operación. Eso hará que tengamos códigos de operación diferentes según el tipo y el tamaño de los datos que se deben transferir y para indicar implícitamente la localización del operando fuente o destino.
STORx: Indica que transferimos un dato hacia la memoria.
STORB [1000], 020h: Indica que transferimos 1 byte a la posición de memoria 1000.
STORW [1000], R1: Indica que transferimos 2 bytes (1 word), el contenido del registro R1, a las posiciones de memoria 1000 y 1001.
En las arquitecturas de tipo registro-registro se utilizan las instrucciones LOAD y STORE cuando hay que efectuar transferencias de datos con la memoria.
Ejemplo de instrucciones de transferencia de datos en la arquitectura MIPS |
En las arquitecturas de tipo registro-memoria o memoria-memoria se utilizan instrucciones MOV cuando hay que llevar a cabo transferencias de datos con la memoria. Es el caso de las arquitecturas Intel x86-64 y CISCA.
Ejemplo de instrucciones de transferencia de datos en la arquitectura Intel x86-64 |
|
5.1.6.3. Instrucciones aritméticas.
Todas las máquinas incluyen instrucciones aritméticas básicas (suma, resta, multiplicación y división) y otras como negar, incrementar, decrementar o comparar.
Cuando hacemos operaciones aritméticas, hemos de tener presentes los tipos de operandos y si los debemos considerar números con signo o sin signo, ya que en algunas operaciones, como la multiplicación o la división, eso puede dar lugar a instrucciones diferentes.
En este subapartado nos centraremos solo en las operaciones aritméticas de números enteros con signo y sin signo. El formato habitual para representar números enteros con signo es el complemento a 2.
Para la representación de números decimales en punto fijo se puede utilizar la misma representación que para los enteros haciendo que la coma binaria esté implícita en alguna posición del número. Para la representación en punto flotante será necesario especificar un formato diferente para representar los números y las instrucciones específicas para operar con ellos.
Suma y resta
Estas operaciones hacen la suma y la resta de dos números; en algunos procesadores se pueden hacer teniendo en cuenta el valor del bit de transporte como bit de transporte inicial.
Ejemplo de suma y resta en la arquitectura CISCA |
Ejemplo de suma y resta en la arquitectura Intel x86-64 considerando el bit de transporte inicial |
Multiplicación
Esta operación efectúa la multiplicación entera de dos números y hay que tener presente que el resultado que se genera tiene un tamaño superior al de los operandos fuente.
Habrá que disponer de dos operaciones diferentes si se quiere tratar operandos con signo o sin signo.
Ejemplo de una instrucción de multiplicación en la arquitectura Intel x86-64
Un operando fuente puede ser implícito y corresponder al registro AL (8 bits), al registro AX (16 bits), al registro EAX (32 bits) o al registro RAX (64 bits); el otro operando fuente es explícito y puede ser un operando de 8, 16, 32 o 64 bits
- Si el operando fuente es de 8 bits, la operación que se hace es AX = AL * fuente, que amplía el resultado a un valor de 16 bits.
- Si el operando fuente es de 16 bits, la operación que se hace es DX,AX = AX * fuente, que amplía el resultado a un valor de 32 bits.
- Si el operando fuente es de 32 bits, la operación que se hace es EDX,EAX = EAX * fuente, que amplía el resultado a un valor de 64 bits.
- Si el operando fuente es de 64 bits, la operación que se hace es RDX,RAX = RAX * fuente, que amplía el resultado a un valor de 128 bits
División
- Operando fuente de 8 bits: (cociente) AL = AX / fuente, (residuo) AH = AX mod fuente
- Operando fuente de 16 bits: (cociente) AX = DX:AX / fuente, (residuo) DX = DX:AX mod fuente
- Operando fuente de 32 bits: (cociente) EAX = EDX:EAX / fuente, (residuo) EDX = EDX:EAX mod fuente
- Operando fuente de 64 bits: (cociente) RAX = RDX:RAX / fuente, (residuo) RDX = RDX:RAX mod fuente
5.1.6.4. Instrucciones lógicas.
Hay varias instrucciones lógicas:
1) Operaciones lógicas. Las instrucciones que hacen operaciones lógicas permiten manipular de manera individual los bits de un operando. Las operaciones lógicas más habituales son AND, OR, XOR, NOT.
Las instrucciones lógicas hacen la operación indicada bit a bit; es decir, el resultado que se produce en un bit no afecta al resto.
5.1.6.5. Instrucciones de ruptura de secuencia.
Las instrucciones de ruptura de secuencia permiten cambiar la secuencia de ejecución de un programa. En el ciclo de ejecución de las instrucciones, el PC se actualiza de manera automática apuntando a la instrucción almacenada a continuación de la que se está ejecutando; para poder cambiarla, hay que saber cuál es la siguiente instrucción que se debe ejecutar para cargar la dirección en el PC.
Entre las instrucciones de ruptura de secuencia encontramos las siguientes:
- Instrucciones de salto o bifurcación.
- Instrucciones de salto incondicional.
- Instrucciones de salto condicional.
- Instrucciones de llamada y retorno de subrutina.
- Instrucciones de interrupción de software y retorno de una rutina de servicio de interrupción.
Instrucciones de salto incondicional
Las instrucciones de salto incondicional cargan en el registro del PC la dirección especificada por el operando, que se expresa como una etiqueta que representa la dirección de la instrucción que se debe ejecutar.
Si se codifica el operando como una dirección, actualizaremos el registro PC con esta dirección. Si se codifica el operando como un desplazamiento, deberemos sumar al PC este desplazamiento y, como veremos más adelante, esta manera de obtener la dirección de salto se denomina direccionamiento relativo a PC.
Instrucciones de salto condicional
Ejemplo de instrucciones de salto condicional comunes a la arquitectura Intel x86-64 y CISCA |
5.1.6.6. Instrucciones de entrada/salida.
Las instrucciones de entrada/salida permiten leer y escribir en un puerto de E/S. Los puertos de E/S se utilizan para acceder a un registro del módulo de E/S que controla un dispositivo o más, para consultar su estado o programarlo.
Según la arquitectura del computador, podemos tener un mapa común de memoria y E/S o un mapa independiente de E/S:
- Mapa común de memoria y E/S: se utilizan las mismas instrucciones de transferencia de datos explicados anteriormente.
- Mapa independiente de E/S: las instrucciones utilizadas para acceder a los puertos de E/S son diferentes de las de transferencia de datos. Las más habituales son:
- IN. Permite leer de un puerto de E/S. Utiliza dos operandos: un número de puerto y una localización para almacenar el valor leído (normalmente un registro).
- OUT. Permite escribir en un puerto de E/S. Utiliza dos operandos: un número de puerto y la localización del valor que se debe escribir en el puerto, habitualmente este valor se encuentra en un registro.
5.1.6.7. Otros tipos de instrucciones.
En un juego de instrucciones suele haber otros tipos de instrucciones de carácter más específico; algunas pueden estar restringidas al sistema operativo para ser ejecutado en modo privilegiado.
Las instrucciones de control del sistema, normalmente, son instrucciones privilegiadas que en algunas máquinas se pueden ejecutar solo cuando el procesador se encuentra en estado privilegiado, como por ejemplo HALT (detiene la ejecución del programa), WAIT (espera por algún acontecimiento para sincronizar el procesador con otras unidades) y otras para gestionar el sistema de memoria virtual o acceder a registros de control no visibles al programador.
Instrucciones específicas para trabajar con números en punto flotante (juego de instrucciones de la FPU) o para cuestiones multimedia, muchas de tipo SIMD (single instruction multiple data).
U otras, como, por ejemplo, la instrucción NOP (no operación), que no hace nada. Aunque el abanico puede ser muy amplio.
5.1.7. Diseño del juego de instrucciones.
El juego de instrucciones de una arquitectura es la herramienta que tiene el programador para controlar el computador; por lo tanto, debería facilitar y simplificar la tarea del programador. Por otra parte, las características del juego de instrucciones condicionan el funcionamiento del procesador y, por ende, tienen un efecto significativo en el diseño del procesador. Así, nos encontramos con el problema de que muchas de las características que facilitan la tarea al programador dificultan el diseño del procesador; por este motivo, es necesario llegar a un compromiso entre facilitar el diseño del computador y satisfacer las necesidades del programador. Una manera de alcanzar este compromiso es diseñar un juego de instrucciones siguiendo un criterio de ortogonalidad.
La ortogonalidad consiste en que todas las instrucciones permitan utilizar como operando cualquiera de los tipos de datos existentes y cualquiera de los modos de direccionamiento, y en el hecho de que la información del código de operación se limite a la operación que debe hacer la instrucción.
Si el juego de instrucciones es ortogonal, será regular y no presentará casos especiales, lo que facilitará la tarea del programador y la construcción de compiladores.
Los aspectos más importantes que hay que tener en cuenta para diseñar un juego de instrucciones son los siguientes:
- Conjunto de operaciones: identificación de las operaciones que hay que llevar a cabo y su complejidad.
- Tipos de datos: identificación de los tipos de datos necesarios para llevar a cabo las operaciones.
- Registros: identificación del número de registros del procesador.
- Direccionamiento: identificación de los modos de direccionamiento que se pueden utilizar en los operandos.
- Formato de las instrucciones: longitud y número de operandos, y tamaño de los diferentes campos.
5.2. Modos de direccionamiento.
Los operandos de una instrucción pueden expresar directamente un dato, la dirección, o la referencia a la dirección, donde tenemos el dato. Esta dirección puede ser la de un registro o la de una posición de memoria, y en este último caso la denominaremos dirección efectiva.
5.2.1. Direccionamiento inmediato.
En el direccionamiento inmediato, el operando expresa el valor del dato que se quiere utilizar; es decir, el dato está dentro de la instrucción y su valor es fijo.
Este modo de direccionamiento se suele utilizar en operaciones aritméticas o lógicas, transferencias en las que se quiere inicializar registros y, de manera general, para definir y utilizar constantes.
El valor del dato se representa, normalmente, en complemento a 2 y cuando se transfiere a un registro o a una posición de memoria se hace la extensión de signo replicando el bit de signo a la izquierda hasta llenar el operando destino.
Ejemplo:
Tenemos un operando inmediato de 12 bits en complemento a 2 y queremos transferir el número –5110 a un registro de 16 bits.
5.2.2. Direccionamiento directo.
En el direccionamiento directo el operando indica dónde se encuentra el dato que se quiere utilizar. Si hace referencia a un registro de la máquina, el dato estará almacenado en este registro y hablaremos de direccionamiento directo a registro; si hace referencia a una posición de memoria, el dato estará almacenado en esta dirección de memoria (dirección efectiva) y hablaremos de direccionamiento directo a memoria.
Ejemplo de direccionamiento a registro y a memoria en la arquitectura CISCA |
5.2.3. Direccionamiento indirecto.
En el direccionamiento indirecto, el operando indica dónde está almacenada la dirección de memoria (dirección efectiva) que contiene el dato que queremos utilizar. Si hace referencia a un registro de la máquina, la dirección de memoria (dirección efectiva) que contiene el dato estará en este registro y hablaremos de direccionamiento indirecto a registro; si hace referencia a una posición de memoria, la dirección de memoria (dirección efectiva) que contiene el dato estará almacenada en esta posición de memoria y hablaremos de direccionamiento indirecto a memoria.
Ya hemos comentado que uno de los problemas del direccionamiento directo a memoria es que se necesitan direcciones muy grandes para poder acceder a toda la memoria; con el modo de direccionamiento indirecto esto no sucede: se puede guardar toda la dirección en un registro o en la memoria utilizando las posiciones que sean necesarias.
Si se guardan en registros, puede suceder que no todos los registros del procesador se puedan utilizar para almacenar estas direcciones, pero en este caso siempre habrá algunos especializados para poder hacerlo, ya que son imprescindibles para que el procesador funcione.
Ejemplo de direccionamiento indirecto a registro en la arquitectura CISCA |
Con la ejecución de esta instrucción se quiere transferir el contenido del registro R12 a la dirección de memoria 0AB00100h. El operando destino hace referencia al registro R1, que contiene la dirección 0AB00100h, donde se tiene que guardar el dato; el operando fuente hace referencia al registro R12, donde se tiene que leer el dato 01234567h. En el operando destino tendremos un direccionamiento indirecto a registro y en el operando fuente, un direccionamiento directo a registro.
5.2.4. Direccionamiento relativo.
5.2.4.1. Direccionamiento relativo a registro base.
En el direccionamiento relativo a registro base, la dirección de memoria se almacena en el registro que denominaremos registro base (RB) y el desplazamiento se encuentra explícitamente en la instrucción. Es más compacto que el modo de direccionamiento absoluto a memoria, ya que el número de bits utilizados para el desplazamiento es inferior al número de bits de una dirección de memoria.
En algunas instrucciones el registro base se utiliza de manera implícita y, por lo tanto, solo habrá que especificar explícitamente el desplazamiento.
Este modo de direccionamiento se utiliza a menudo para implementar la segmentación. Algunas máquinas tienen registros específicos para eso y se utilizan de manera implícita, mientras que en otras máquinas se pueden elegir los registros que se quieren utilizar como base del segmento, pero, entonces, hay que especificarlo explícitamente en la instrucción.
Segmentación de los Intel x86-64En el caso del Intel x86-64, se implementa la segmentación utilizando registros de segmento específicos (CS:codigo, DS:datos, SS:pila y ES, FS, GS:extra), pero también se tiene la flexibilidad de poder reasignar algunos, tanto si es de manera explícita en las instrucciones o mediante directivas de compilación como la ASSUME.
5.2.4.2. Direccionamiento relativo a registro índice.
En el direccionamiento relativo a registro índice, la dirección de memoria se encuentra explícitamente en la instrucción y el desplazamiento se almacena en el registro que denominaremos registro índice (RI). Justo al contrario que en el direccionamiento relativo a registro base.
Este modo de direccionamiento se utiliza a menudo para acceder a estructuras de datos como vectores, matrices o listas, por lo que es muy habitual que después de cada acceso se incremente o decremente el registro índice un valor constante (que depende del tamaño y el número de posiciones de memoria de los datos a los que se accede). Por este motivo, algunas máquinas hacen esta operación de manera automática y proporcionan diferentes alternativas que denominaremos direccionamientos relativos autoindexados. Si la autoindexación se lleva a cabo sobre registros de carácter general, puede requerir un bit extra para indicarlo en la codificación de la instrucción.
Es muy habitual permitir tanto el autoincremento como el autodecremento, operación que se puede realizar antes de obtener la dirección efectiva o después. Por lo tanto, habrá cuatro alternativas de implementación, aunque un mismo juego de instrucciones no las tendrá simultáneamente:
- Preautoincremento: RI se incrementa antes de obtener la dirección.
- Preautodecremento: RI se decrementa antes de obtener la dirección.
- Postautoincremento: RI se incrementa después de obtener la dirección.
- Postautodecremento: RI se decrementa después de obtener la dirección.
5.2.4.3. Direccionamiento relativo a la computadora.
El direccionamiento relativo a PC es equivalente al relativo a registro base, con la diferencia de que utiliza el registro contador de programa (PC) de manera implícita en la instrucción y solo hay que expresar, mediante una etiqueta, el desplazamiento para calcular la dirección de memoria (dirección efectiva) a la que se quiere acceder.
Otra característica que diferencia el modo de direccionamiento relativo a PC es la manera de expresar el desplazamiento: como puede ser un valor tanto positivo como negativo, se suele representar en complemento a 2.
El direccionamiento relativo a PC se utiliza a menudo en las instrucciones de ruptura de secuencia, generalmente en los saltos condicionales, que suelen ser cortos y, en consecuencia, el tamaño del campo no tendrá que ser muy grande.
Tenemos el fragmento de código siguiente:
|
En este código, cada instrucción ocupa 7 bytes, salvo la instrucción JE bucle, que ocupa 4 bytes. La etiqueta bucle hace referencia a la dirección de memoria 00001007h. La instrucción JE bucle (salta si el bit de cero está activo) utiliza direccionamiento relativo a PC y, por lo tanto, no codifica la dirección de memoria a la que se quiere saltar, sino el desplazamiento respecto al PC actualizado utilizando 16 bits (desplazamiento = etiqueta-PCupdated).
En el ciclo de ejecución de la instrucción, la actualización del PC se realiza en las primeras fases de este ciclo, por lo tanto, al final de la ejecución de la instrucción (que es cuando sabremos si hemos de saltar) el PC no apuntará a la instrucción de salto (JE bucle; PC=00011015h), sino a la instrucción siguiente (MOV R0,–1; PCupdated = 00011019h); es decir, deberemos saltar tres instrucciones atrás y, como la instrucción JE bucle ocupa 4 bytes y el resto, 7 bytes cada una, el desplazamiento será de –18; para obtener este valor restaremos de la dirección de la etiqueta bucle el valor de PCupdated (00011007h – 000110119h = FFEEh(–18 en Ca2)). Este valor, FFEEh, es el que se codificará como desplazamiento en la instrucción de salto condicional.
Entonces, cuando se ejecuta una instrucción de salto condicional, si se cumple la condición se actualiza el PC sumándole el desplazamiento que tenemos codificado. En nuestro caso, después de ejecutar la instrucción de salto condicional JE bucle si el bit de cero C está activo, saltamos y PC valdrá 00011007h y si el bit de cero C no está activo, saltamos y PC valdrá 00011019h.
5.2.5. Direccionamiento implícito.
En el direccionamiento implícito, la instrucción no contiene información sobre la localización del operando porque este se encuentra en un lugar predeterminado, y queda especificado de manera implícita en el código de operación.
5.2.5.1. Direccionamiento a pila.
Pilauna pila es una lista de elementos con el acceso restringido, de manera que solo se puede acceder a los elementos de un extremo de la lista. Este extremo se denomina cima de la pila. El último elemento que entra a la pila es el primero que sale (LIFO). A medida que se van añadiendo elementos, la pila crece y según la implementación lo hará hacia direcciones más pequeñas (método más habitual) o hacia direcciones mayores.
0 Comentarios