Está en la página 1de 90

Arquitectura

6
6.1
6.2
Introducción

Lenguaje ensamblador

6.3 Programación

6.4 Lenguaje de máquina

6.5 Luces, CAMARA, ACCION:


Compilar, ensamblar y
Cargando*

6.6 Retazos*

6.1 INTRODUCCIÓN 6,7 Evolución de ARM


Arquitectura
Los capítulos anteriores introdujeron los principios y componentes básicos del
6,8 Otra perspectiva: x86
diseño digital. En este capítulo, subimos algunos niveles de abstracción para definir
Arquitectura
la arquitectura de una computadora. El arquitectura es el programador ' s vista de
6,9 Resumen
una computadora. Está definido por el conjunto de instrucciones (idioma) y las
Ejercicios
ubicaciones de los operandos (registros y memoria). Existen muchas arquitecturas
diferentes, como ARM, x86, MIPS, SPARC y PowerPC. Preguntas de entrevista

El primer paso para comprender cualquier arquitectura de computadora es


aprender su idioma. Las palabras en una computadora ' s lenguaje se llama instrucciones. Solicitud > ”Hola
Software ¡mundo!"
El ordenador ' El vocabulario se llama conjunto de instrucciones. Todos los programas que
se ejecutan en una computadora utilizan el mismo conjunto de instrucciones. Incluso las Operando
aplicaciones de software complejas, como el procesamiento de textos y las aplicaciones Sistemas

de hojas de cálculo, eventualmente se compilan en una serie de instrucciones simples


como sumar, restar y ramificar. Las instrucciones de la computadora indican tanto la Arquitectura

operación a realizar como los operandos a usar. Los operandos pueden provenir de la
Micro-
memoria, de los registros o de la propia instrucción.
arquitectura
El hardware de la computadora solo comprende 1 ' sy 0 ' s, entonces las instrucciones son
codificados como números binarios en un formato llamado lenguaje de máquina. Así como +
Lógica
usamos letras para codificar el lenguaje humano, las computadoras usan números binarios
para codificar el lenguaje de la máquina. ARMarchitecture representa cada instrucción como Digital
una palabra de 32 bits. Los microprocesadores son sistemas digitales que leen y ejecutan Circuitos

instrucciones en lenguaje de máquina. Sin embargo, los humanos consideran que leer el
Cosa análoga
+
lenguaje de máquina es tedioso, por lo que preferimos representar las instrucciones en un Circuitos -

formato simbólico llamado lenguaje ensamblador.


Los conjuntos de instrucciones de diferentes arquitecturas se parecen más a Dispositivos

diferentes dialectos que a diferentes idiomas. Casi todas las arquitecturas definen
instrucciones básicas, como sumar, restar y bifurcar, que operan en memoria o Física
registros. Una vez que haya aprendido un conjunto de instrucciones, comprender
las demás es bastante sencillo.

295
296 CAPITULO SEIS Arquitectura

Una arquitectura de computadora no define la implementación de hardware


subyacente. A menudo, existen muchas implementaciones de hardware diferentes
de una sola arquitectura. Por ejemplo, Intel y Advanced Micro Devices (AMD)
venden varios microprocesadores que pertenecen a la misma arquitectura x86.
Todos pueden ejecutar los mismos programas, pero utilizan hardware subyacente
diferente y, por lo tanto, ofrecen compensaciones en rendimiento, precio y
potencia. Algunos microprocesadores están optimizados para servidores de alto
rendimiento, mientras que otros están optimizados para una batería de larga
duración en computadoras portátiles. La disposición específica de registros,
memorias, ALU y otros bloques de construcción para formar un microprocesador se
llama microarquitectura y será el tema del Capítulo 7. A menudo, existen muchas
microarquitecturas diferentes para una sola arquitectura.
En este texto, presentamos la arquitectura ARM. Esta arquitectura fue desarrollada
El " Arquitectura ARM " que por primera vez en la década de 1980 por Acorn Computer Group, que se separó de
describimos es ARM versión 4 Advanced RISC Machines Ltd., ahora conocida como ARM. Cada año se venden más de 10
(ARMv4), que forma el núcleo del mil millones de procesadores ARM. Casi todos los teléfonos móviles y tabletas contienen
conjunto de instrucciones.
varios procesadores ARM. La arquitectura se utiliza en todo, desde máquinas de pinball
Sección 6.7 resume las nuevas
hasta cámaras, robots, automóviles y servidores montados en bastidor. ARM es inusual
funciones en las versiones 5 - 8 de la
en el sentido de que no vende procesadores directamente, sino que otorga licencias a
arquitectura. El brazo
otras compañías para construir sus procesadores, a menudo como parte de un sistema
Arquitectura de referencia
Manual ( ARM), disponible
en chip más grande. Por ejemplo, Samsung, Altera, Apple y Qualcommall construyen
online, es la definición autorizada procesadores ARM, ya sea utilizando microarquitecturas compradas a ARM o
de la arquitectura. microarquitecturas desarrolladas internamente bajo licencia de ARM. Elegimos
enfocarnos en ARM porque es un líder comercial y porque la arquitectura es limpia, con
pocas idiosincrasias. Comenzamos presentando instrucciones en lenguaje ensamblador,
ubicaciones de operandos y construcciones de programación comunes, como ramas,
bucles, manipulaciones de matrices y llamadas a funciones. Luego describimos cómo se
traduce el lenguaje ensamblador al lenguaje de máquina y mostramos cómo se carga y
ejecuta un programa en la memoria.
A lo largo del capítulo, motivamos el diseño de la arquitectura ARM
utilizando cuatro principios articulados por David Patterson y John Hennessy
en su texto. Organización y diseño de computadoras: ( 1) la regularidad apoya
la simplicidad; (2) acelerar el caso común; (3) más pequeño es más rápido; y (4)
un buen diseño exige buenos compromisos.

6.2 LENGUAJE DE MONTAJE


Lenguaje ensamblador es la representación legible por humanos de la
computadora ' s lengua materna. Cada instrucción en lenguaje ensamblador
especifica tanto la operación a realizar como los operandos en los que operar.
Introducimos instrucciones aritméticas simples y mostramos cómo se escriben
estas operaciones en lenguaje ensamblador. Luego definimos los operandos de la
instrucción ARM: registros, memoria y constantes.
Este capítulo asume que ya está familiarizado con un lenguaje de
programación de alto nivel como C, C ++ o Java.
6.2 Lenguaje ensamblador 297

(Estos lenguajes son prácticamente idénticos para la mayoría de los ejemplos de


Usamos keil ' s BRAZO
este capítulo, pero donde difieren, usaremos C.) El Apéndice C proporciona una
Desarrollo de microcontroladores
introducción a C para aquellos con poca o ninguna experiencia previa en Kit (MDK-ARM) para compilar,
programación. ensamblar y simular el código
ensamblador de ejemplo en este
6. 2. 1 Instrucciones capítulo. MDK-ARM es una herramienta
de desarrollo gratuita que viene con un
La operación más común que realizan las computadoras es la adición. El ejemplo de
compilador ARM completo.
código 6.1 muestra el código para agregar variables B y C y escribiendo el resultado en una. Laboratorios disponibles en este libro
El código se muestra a la izquierda en un lenguaje de alto nivel (usando la sintaxis de C, C de texto ' El sitio complementario
++ y Java) y luego se reescribe a la derecha en lenguaje ensamblador ARM. Tenga en (consulte el Prefacio) muestra cómo
cuenta que los enunciados de un programa en C terminan con punto y coma. instalar y utilizar esta herramienta para
escribir, compilar, simular y depurar
programas C y ensambladores.

Ejemplo de código 6.1 ADICIÓN

Código de alto nivel Código de ensamblaje ARM

a = b + c; AÑADIR a, b, c

La primera parte de las instrucciones de montaje, AGREGAR, se llama el mnemotécnico


Mnemónico (pronunciado
e indica qué operación realizar. La operación se realiza en B y C, ni-mon-ik) proviene de la palabra
la operandos fuente, y el resultado se escribe en a, la operando de destino. griega μ ι μ νΕσκεστηαι, recordar. La
Asamblea
El lenguaje mnemónico es más fácil
Ejemplo de código 6.2 SUSTRACCIÓN de recordar que un patrón de
lenguaje de máquina de 0 ' sy 1 ' s
Código de alto nivel Código de ensamblaje ARM representa lo mismo
a = b - C; SUB a, b, c operación.

El ejemplo de código 6.2 muestra que la resta es similar a la suma. El formato de


instrucción es el mismo que el AGREGAR instrucción a excepción de la especificación de
operación, SUB. Este formato de instrucción consistente es un ejemplo del primer
principio de diseño:

Principio de diseño 1: La regularidad apoya la simplicidad.

Instrucciones con un número constante de operandos - en este caso, dos


fuentes y un destino - son más fáciles de codificar y manejar en hardware. El
código de alto nivel más complejo se traduce en varias instrucciones ARM,
como se muestra en el ejemplo de código 6.3.
En los ejemplos de lenguaje de alto nivel, los comentarios de una sola línea comienzan con
// y continuar hasta el final de la línea. Los comentarios de varias líneas comienzan con
/ * y terminar con * /. En lenguaje ARMassembly, solo comentarios de una sola línea
298 CAPITULO SEIS Arquitectura

Ejemplo de código 6.3 CÓDIGO MÁS COMPLEJO

Código de alto nivel Código de ensamblaje ARM

a = b + c - D; // comentario de una sola línea AÑADIR t, b, c; t = b + c SUB a,


/ * multilínea t, d; a = t - D
comentario * /

son usados. Comienzan con un punto y coma (;) y continúan hasta el final de la línea. El
programa en lenguaje ensamblador en el ejemplo de código 6.3 requiere una variable
temporal t para almacenar el resultado intermedio. El uso de múltiples instrucciones en
lenguaje ensamblador para realizar operaciones más complejas es un ejemplo del
segundo principio de diseño de la arquitectura de la computadora:

Principio de diseño 2: Haz que el caso común sea rápido.

El conjunto de instrucciones ARM agiliza el caso común al incluir solo


instrucciones simples y de uso común. El número de instrucciones se mantiene
pequeño para que el hardware necesario para decodificar la instrucción y sus
operandos pueda ser simple, pequeño y rápido. Las operaciones más elaboradas
que son menos comunes se realizan utilizando secuencias de múltiples
instrucciones simples. Por tanto, ARM es un Grupo reducido de instrucciones para
computadoras ( Arquitectura RISC). Arquitecturas con muchas instrucciones
complejas, como Intel ' s arquitectura x86, son Computadoras de conjunto de
instrucciones complejas CISC). Por ejemplo, x86 define un " movimiento de cuerda " instrucción
que copia una cadena (una serie de caracteres) de una parte de la memoria a otra.
Tal operación requiere muchas, posiblemente incluso cientos, de instrucciones
simples en una máquina RISC. Sin embargo, el costo de implementar instrucciones
complejas en una arquitectura CISC se agrega al hardware y la sobrecarga que
ralentiza las instrucciones simples.
La arquitectura ARISC minimiza la complejidad del hardware y la codificación de
instrucciones necesaria al mantener pequeño el conjunto de instrucciones distintas. Por
ejemplo, un conjunto de instrucciones con 64 instrucciones simples necesitaría
Iniciar sesión 2 64 = 6 bits para codificar la operación. Un conjunto de instrucciones con 256 com
las instrucciones plex necesitarían registro 2 256 = 8 bits de codificación por instrucción. En
una máquina CISC, aunque las instrucciones complejas solo pueden usarse
raramente, agregan sobrecarga a todas las instrucciones, incluso a las simples.

6. 2. 2 Operandos: registros, memoria y constantes

Una instrucción opera en operandos. En el ejemplo de código 6.1, las variables


a, b, y C son todos operandos. Pero las computadoras operan en 1 ' sy 0 ' s, no nombres de
variables. Las instrucciones necesitan una ubicación física desde la que recuperar los
datos binarios. Los operandos pueden almacenarse en registros o memoria, o pueden
ser constantes almacenadas en la propia instrucción. Uso de computadoras
6.2 Lenguaje ensamblador 299

Varias ubicaciones para contener operandos con el fin de optimizar la velocidad y la


Versión 8 del ARM
capacidad de datos. Se accede rápidamente a los operandos almacenados como
La arquitectura se ha ampliado a 64 bits,
constantes o en registros, pero solo contienen una pequeña cantidad de datos. Se debe
pero en este libro nos centraremos en la
acceder a datos adicionales desde la memoria, que es grande pero lenta. ARM (antes de versión de 32 bits.
ARMv8) se denomina arquitectura de 32 bits porque opera con datos de 32 bits.

Registros
Las instrucciones necesitan acceder a los operandos rápidamente para que puedan ejecutarse
rápidamente. Pero los operandos almacenados en la memoria tardan mucho en recuperarse. Por lo
tanto, la mayoría de las arquitecturas especifican una pequeña cantidad de registros que contienen
operandos de uso común. La arquitectura ARM utiliza 16 registros, llamados conjunto de registro
o registro de archivo. Cuanto menos registros, más rápido se puede acceder a ellos. Esto
conduce al tercer principio de diseño:

Principio de diseño 3: Cuanto más pequeño, más rápido.

Buscar información de una pequeña cantidad de libros relevantes en su escritorio es


mucho más rápido que buscar la información en las estanterías de una biblioteca.
Asimismo, leer datos de un archivo de registro pequeño es más rápido que leerlos de
una memoria grande. Por lo general, un archivo de registro se construye a partir de una
pequeña matriz SRAM (consulte la Sección 5.5.3).
El ejemplo de código 6.4 muestra el AGREGAR instrucción con operandos de registro. Los
nombres de los registros ARM están precedidos por la letra 'R'. Las variables a, b, y
C se colocan arbitrariamente en R0, R1 y R2. El nombre R1 se pronuncia
" registro 1 " o " R1 " o " registrar R1 ”. La instrucción suma los valores de 32 bits
contenidos en R1 ( B) y R2 ( C) y escribe el resultado de 32 bits en R0 ( a). El ejemplo
de código 6.5 muestra el código ensamblador ARM usando un registro, R4, para
almacenar el cálculo intermedio de b + c:

Ejemplo de código 6.4 REGISTRAR OPERANDOS

Código de alto nivel Código de ensamblaje ARM

a = b + c; ; R0 = a, R1 = b, R2 = c
AÑADIR R0, R1, R2 ;a=b+c

Ejemplo de código 6.5 REGISTROS TEMPORALES

Código de alto nivel Código de ensamblaje ARM

a = b + c - D; ; R0 = a, R1 = b, R2 = c, R3 = d; R4 = t
AÑADIR R4, R1, R2; t = b + c SUB R0,
R4, R3; a = t - D
300 CAPITULO SEIS Arquitectura

Ejemplo 6.1 TRADUCIR CÓDIGO DE ALTO NIVEL A MONTAJE


IDIOMA

Traduzca el siguiente código de alto nivel al lenguaje ensamblador ARM. Asume variables a
- C se mantienen en los registros R0 - R2 y F - j están en R3 - R7.

a = b - C;
f = (g + h) - ( i + j);

Solución: El programa utiliza cuatro instrucciones en lenguaje ensamblador.

; Código de ensamblaje ARM


; R0 = a, R1 = b, R2 = c, R3 = f, R4 = g, R5 = h, R6 = i, R7 = j
SUB R0, R1, R2 ;a=b-C
AÑADIR R8, R4, R5 ; R8 = g + h
AÑADIR R9, R6, R7 ; R9 = yo + j
SUB R3, R8, R9 ; f = (g + h) - ( yo + j)

El conjunto de registro

Cuadro 6.1 enumera el nombre y el uso de cada uno de los 16 registros ARM. R0 - Los R12 se
utilizan para almacenar variables; R0 - R3 también tiene usos especiales durante las llamadas a
procedimientos. R13 - Los R15 también se denominan SP, LR y PC, y se describirán más adelante
en este capítulo.

Constantes / Inmediatos
Además de las operaciones de registro, las instrucciones ARM pueden usar constantes o
inmediato operandos. Estas constantes se denominan inmediatas, porque sus
valores están disponibles inmediatamente en la instrucción y no requieren un
registro o acceso a la memoria. El ejemplo de código 6.6 muestra el AGREGAR instrucción
agregando un inmediato a un registro. En código ensamblador, el inmediato está
precedido por el símbolo # y puede escribirse en decimal o hexadecimal. Las
constantes hexadecimales en lenguaje ensamblador ARM comienzan con 0x, ya que

Cuadro 6.1 Conjunto de registro ARM

Nombre Usar

R0 Argumento / valor de retorno / variable temporal

R1 - R3 Argumento / variables temporales

R4 - R11 Variables guardadas

R12 Variable temporal

R13 (SP) Puntero de pila

R14 (LR) Registro de enlace

R15 (PC) Contador de programa


6.2 Lenguaje ensamblador 301

Ejemplo de código 6.6 OPERACIONES INMEDIATAS

Código de alto nivel Código de ensamblaje ARM

a = a + 4; ; R7 = a, R8 = b
b = a - 12; AÑADIR R7, R7, # 4 ;a=a+4
SUB R8, R7, # 0xC; b = a - 12

Ejemplo de código 6.7 INICIALIZANDO VALORES USANDO INMEDIATOS

Código de alto nivel Código de ensamblaje ARM

i = 0; ; R4 = yo, R5 = x
x = 4080; MOV R4, # 0 ;i=0
MOV R5, # 0xFF0 ; x = 4080

hacer en C.Los inmediatos son números sin signo de 8 a 12 bits con una
codificación peculiar descrita en Sección 6.4 . La instrucción de movimiento ( MOV) es
una forma útil de inicializar valores de registro. El ejemplo de código 6.7 inicializa
las variables I y X a 0 y 4080, respectivamente. MOV también puede tomar un
operando de origen de registro. Por ejemplo, MOV R1, R7 copia el contenido del
registro R7 en R1.

Memoria
Si los registros fueran el único espacio de almacenamiento para operandos, estaríamos
confinados a programas simples con no más de 15 variables. Sin embargo, los datos también se
pueden almacenar en la memoria. Mientras que el archivo de registro es pequeño y rápido, la
memoria es más grande y más lenta. Por esta razón, las variables de uso frecuente se
mantienen en registros. En la arquitectura ARM, las instrucciones operan exclusivamente en
registros, por lo que los datos almacenados en la memoria deben moverse a un registro antes
de que puedan procesarse. Al utilizar una combinación de memoria y registros, un programa
puede acceder a una gran cantidad de datos con bastante rapidez. Recuperar de la sección
5.5 que las memorias están organizadas como una matriz de palabras de datos. La arquitectura
ARM utiliza direcciones de memoria de 32 bits y palabras de datos de 32 bits.
ARM usa un direccionable por byte memoria. Es decir, cada byte de la memoria tiene una
dirección única, como se muestra en Figura 6.1 (a) . Una palabra de 32 bits consta de cuatro
bytes de 8 bits, por lo que cada dirección de palabra es un múltiplo de 4. El byte más
significativo (MSB) está a la izquierda y el byte menos significativo (LSB) está a la derecha. Tanto
la dirección de palabra de 32 bits como el valor de datos en Figura 6.1 (b) se dan en
hexadecimal. Por ejemplo, la palabra de datos 0xF2F1AC07 se almacena en la dirección de
memoria 4. Por convención, la memoria se extrae con direcciones de memoria baja hacia la
parte inferior y direcciones de memoria alta hacia la parte superior.
ARM proporciona el registro de carga instrucción, LDR, para leer una palabra de datos de
la memoria en un registro. El ejemplo de código 6.8 carga la palabra de memoria 2 en
a ( R7). En C, el número dentro de los corchetes es el índice o número de palabra,
302 CAPITULO SEIS Arquitectura

Dirección de byte Dirección de palabra Datos Número de palabra

13 12 11 10 00000010 CD 1 9 A 6 5 B Palabra 4
Figura 6.1 Memoria ARM direccionable por
F mi D C 0000000C 4 0 F 3 0 7 8 8 Palabra 3
byte que muestra: (a) dirección de byte y (b)
B A 9 8 00000008 0 1 EE 2 8 4 2 Palabra 2
datos
7 6 5 4 00000004 F 2 F 1 AC 0 7 Palabra 1

3 2 1 0 00000000 ABCDEF 7 8 Palabra 0


MSB LSB
(a) (B) Ancho = 4 bytes

Ejemplo de código 6.8 LECTURA DE MEMORIA

Código de alto nivel Código de ensamblaje ARM

a = mem [2]; ; R7 = a
MOV R5, # 0 ; dirección base = 0
LDR R7, [R5, nº 8]; R7 <= datos en la dirección de memoria (R5 + 8)

Una lectura de la dirección base (es


que discutimos más adelante en Sección 6.3.6 . El LDR instrucción especifica la dirección de
decir, índice 0) es un caso especial que
memoria usando un registro base R5) y un compensar ( 8). Recuerde que cada palabra de datos
no requiere compensación en el código
tiene 4 bytes, por lo que la palabra número 1 está en la dirección 4, la palabra número 2 está
ensamblador. Por ejemplo, una
en la dirección 8, y así sucesivamente. La palabra dirección es cuatro veces el número de la
memoria leída de la dirección base
contenida en R5 se escribe palabra. La dirección de memoria se forma sumando el contenido del registro base (R5) y el
como LDR R3, [R5]. desplazamiento. ARM ofrece varios modos para acceder a la memoria, como se discutirá en Sección
6.3.6 . Después de la instrucción de registro de carga ( LDR) se ejecuta en el código Ejemplo 6.8,
R7 tiene el valor 0x01EE2842, que es el valor de los datos almacenados en la dirección de
ARMv4 requiere alineado con la palabra
memoria 8 en Figura 6.1 . ARM usa el registro de la tienda instrucción, STR, para escribir una
direcciones por LDR y STR, es decir, una
palabra de datos de un registro en la memoria. El ejemplo de código 6.9 escribe el valor 42 del
dirección de palabra divisible por
registro R9 en la palabra de memoria 5.
cuatro. Desde ARMv6, esta restricción
de alineación se puede eliminar
estableciendo un bit en el registro de
control del sistema ARM, pero el Las memorias direccionables por bytes se organizan de forma big-endian o
rendimiento de desalineado littleendian, como se muestra en Figura 6.2 . En ambos formatos, una palabra de 32 bits ' El
cargas suele ser peor. Algunas byte más significativo (MSB) está a la izquierda y el byte menos significativo (LSB) está a la
arquitecturas, como x86, permiten lecturas derecha. Las direcciones de palabra son las mismas en ambos formatos y se refieren a
y escrituras de datos no alineados con los mismos cuatro bytes. Solo las direcciones de bytes dentro de una palabra
palabras, pero otras, como MIPS,
requieren una alineación estricta para
Ejemplo de código 6.9 MEMORIA DE ESCRITURA
simplificar. Por supuesto, byte
direcciones para el byte de carga y el byte
Código de alto nivel Código de ensamblaje ARM
de almacenamiento, LDRB y STRB
mem [5] = 42; MOV R1, # 0 ; dirección base = 0
(discutido en la Sección 6.3.6), no es necesario que
MOV R9, n.º 42
estén alineados con las palabras. STR R9, [R1, nº 0x14]; valor almacenado en la dirección de memoria (R1 + 20) = 42
6.3 Programación 303

Big-Endian Little-Endian
Byte Palabra Byte
Dirección Dirección Dirección

Figura 6.2 Direccionamiento de


CDEF C FEDC
memoria big-endian y little-endian
8 9 AB 8 BA 9 8
4567 4 7654
0123 0 3210
MSB LSB MSB LSB

diferir de. En big-endian máquinas, los bytes se numeran comenzando con 0 en el


extremo grande (más significativo). En little-endian máquinas, los bytes se numeran
comenzando con 0 en el extremo pequeño (menos significativo).
IBM ' s PowerPC (anteriormente encontrado en computadoras Macintosh) usa
direccionamiento bigendiano. Intel ' La arquitectura x86 (que se encuentra en las PC)
utiliza direccionamiento littleendian. ARM prefiere little-endian pero proporciona soporte
en algunas versiones para bi-endian direccionamiento de datos, que permite la carga y
almacenamiento de datos en cualquier formato. La elección de endianidad es
completamente arbitraria, pero genera problemas al compartir datos entre
Los términos big-endian y
computadoras big-endian y little-endian. En los ejemplos de este texto, usamos el
littleendian provienen de Jonathan
formato little-endian siempre que el orden de los bytes es importante.
Swift ' s Gulliver ' s viajes, publicado
por primera vez en 1726 bajo el
6.3 PROGRAMACIÓN seudónimo de Isaac
Bickerstaff. En sus historias, el rey
Los lenguajes de software como C o Java se denominan lenguajes de programación de
liliputiense requería que sus
alto nivel porque están escritos en un nivel más abstracto que el lenguaje ensamblador.
ciudadanos (los Little-Endians)
Muchos lenguajes de alto nivel utilizan construcciones de software comunes como
rompieran sus huevos en el extremo
operaciones aritméticas y lógicas, ejecución condicional, declaraciones if / else, bucles for pequeño. Los Big-Endians eran
y while, indexación de matrices y llamadas a funciones. Consulte el Apéndice C para ver rebeldes que rompieron sus huevos en
más ejemplos de estas construcciones en C. En esta sección, exploramos cómo traducir el extremo grande.
estas construcciones de alto nivel en código ensamblador ARM. Estos términos se aplicaron por

primera vez a la computadora.

arquitecturas de Danny Cohen en su

6. 3. 1 Instrucciones de procesamiento de datos artículo " Sobre guerras santas y una


súplica por la paz "
La arquitectura ARM define una variedad de procesamiento de datos instrucción (a publicado el Día de los Inocentes de
menudo llamadas instrucciones lógicas y aritméticas en otras arquitecturas). 1980 ( USC / ISI IEN 137).
Presentamos estas instrucciones brevemente aquí porque son necesarias para (Foto cortesía de The
implementar construcciones de nivel superior. El Apéndice B proporciona un resumen de Colección Brotherton, Leeds

las instrucciones de ARM. Biblioteca Universitaria.)

Instrucciones lógicas
BRAZO operaciones lógicas incluir Y, ORR ( O), EOR ( XOR) y BIC
(un poco claro). Cada uno de estos opera bit a bit en dos fuentes y escribe el resultado
304 CAPITULO SEIS Arquitectura

Registros de origen

R1 0100 0110 1010 0001 1111 0001 1011 0111

R2 1111 1111 1111 1111 0000 0000 0000 0000

Código de ensamblaje Resultado

Figura 6.3 Operaciones lógicas Y R3, R1, R2 R3 0100 0110 1010 0001 0000 0000 0000 0000 1111 1111

ORR R4, R1, R2 R4 1111 1111 1111 0001 1011 0111

EOR R5, R1, R2 R5 1011 1001 0101 1110 1111 0001 0000 1011 0111

BIC R6, R1, R2 R6 0000 0000 0000 1111 0001 0000 0000 1011 0111

MVN R7, R2 R7 0000 0000 1111 1111 1111 1111

a un registro de destino. La primera fuente es siempre un registro y la


segunda fuente es un registro inmediato u otro. Otra operación lógica, MVN ( MoVe
y Not), realiza un NOT bit a bit en la segunda fuente (un registro inmediato o) y
escribe el resultado en el registro de destino. Figura 6.3 muestra ejemplos de
estas operaciones en los dos valores fuente 0x46A1F1B7 y 0xFFFF0000. La
figura muestra los valores almacenados en el registro de destino después de
que se ejecuta la instrucción.
El poco claro BIC) La instrucción es útil para enmascarar bits (es decir, forzar
bits no deseados a 0). BIC R6, R1, R2 calcula R1 Y NO R2. En otras palabras, BIC borra
los bits que se afirman en R2. En este caso, los dos bytes superiores de R1 se borran
o enmascarado, y los dos bytes inferiores sin máscara de R1, 0xF1B7, se colocan en
R6. Se puede enmascarar cualquier subconjunto de bits de registro.

El ORR La instrucción es útil para combinar campos de bits de dos


registros. Por ejemplo, 0x347A0000 ORR 0x000072FC = 0x347A72FC.

Instrucciones de turno

Instrucciones de turno desplaza el valor en un registro hacia la izquierda o hacia la derecha, eliminando bits del
final. La instrucción de rotación rota el valor en un registro a la derecha hasta 31 bits. Nos referimos tanto a
shift como a rotar genéricamente como operaciones de cambio. Las operaciones de cambio de ARM son LSL ( desplazamiento
lógico a la izquierda), LSR ( desplazamiento lógico a la derecha),
ASR ( desplazamiento aritmético a la derecha), y ROR ( Gira a la derecha). No hay ROL
instrucción porque la rotación a la izquierda se puede realizar con una rotación a la derecha por
una cantidad complementaria.
Como se discutió en la Sección 5.2.5, los desplazamientos a la izquierda siempre llenan los
bits menos significativos con 0 ' s. Sin embargo, los cambios a la derecha pueden ser lógicos (0 ' s
cambia a los bits más significativos) o aritmética (el bit de signo cambia a los bits más
significativos). La cantidad por la que cambiar puede ser inmediata o registrada.
Figura 6.4 muestra el código ensamblador y los valores de registro resultantes
para LSL, LSR, ASR, y ROR al cambiar por un valor inmediato. R5 se desplaza por la
cantidad inmediata y el resultado se coloca en el registro de destino.
6.3 Programación 305

Registro de origen

R5 1111 1111 0001 1100 0001 0000 1110 0111

Código de montaje Resultado Figura 6.4 Instrucciones de turno con


LSL R0, R5, n.º 7 R0 1000 1110 0000 1000 0111 0011 1 000 0000 0000 cantidades de turno inmediatas

LSR R1, R5, n.º 17 R1 0000 0000 0000 0 111 1111 1000 1110
ASR R2, R5, n.º 3 R2 111 1 1111 1110 0011 1000 0010 0001 1100
ROR R3, R5, n.º 21 R3 1110 0000 1000 0111 0011 1 111 1111 1000

Registros de origen

R8 0000 1000 0001 1100 0001 0110 1110 0111


R6 0000 0000 0000 0000 0000 0000 0001 0100
Figura 6.5 Instrucciones de turno con
cantidades de turno de registro
Código de ensamblaje Resultado

LSL R4, R8, R6 R4 0110 1110 0111 0000 0000 0000 0000 0000
ROR R5, R8, R6 R5 1100 0001 0110 1110 0111 0000 1000 0001

Cambiar un valor dejado por norte es equivalente a multiplicarlo por 2 NORTE. Del mismo modo, cambiar
aritméticamente un valor a la derecha norte es equivalente a dividirlo por 2 NORTE, como se discutió en la
Sección 5.2.5. Los cambios lógicos también se utilizan para extraer o ensamblar campos de bits.

Figura 6.5 muestra el código ensamblador y los valores de registro resultantes para las
operaciones de cambio donde la cantidad de cambio se mantiene en un registro, R6. Esta
instrucción usa el registro de registro desplazado modo de direccionamiento, donde un registro
(R8) se desplaza por la cantidad (20) retenida en un segundo registro (R6).

Multiplica las instrucciones *

La multiplicación es algo diferente de otras operaciones aritméticas.


Multiplicar dos números de 32 bits produce un producto de 64 bits. La
arquitectura ARM proporciona multiplicar instrucciones que dan como
resultado un producto de 32 o 64 bits. Multiplicar ( MUL) multiplica dos números
de 32 bits y produce un resultado de 32 bits. MUL R1, R2, R3 multiplica los
valores en R2 y R3 y coloca los bits menos significativos del producto en R1; se
descartan los 32 bits más significativos del producto. Esta instrucción es útil
para multiplicar números pequeños cuyo resultado se ajusta a 32 bits. UMULL
(sin firmar multiplicar largo) y SMULL ( con signo multiplicar largo) multiplicar
dos números de 32 bits y producir un producto de 64 bits. Por ejemplo, UMULL
R1, R2, R3, R4 realiza una multiplicación sin signo de R3 y R4. Los 32 bits menos
significativos del producto se colocan en R1 y los 32 bits más significativos se
colocan en R2.
306 CAPITULO SEIS Arquitectura

Cada una de estas instrucciones también tiene una variante de acumulación múltiple,
MLA, SMLAL, y UMLAL, que agrega el producto a una suma corriente de 32 o 64 bits. Estas
instrucciones pueden mejorar el rendimiento matemático en aplicaciones como la
multiplicación de matrices y el procesamiento de señales que consisten en
multiplicaciones y sumas repetidas.
CPSR
31 30 29 28 4 3 2 1 0

NZCV ... M [4: 0]


6. 3. 2 Indicadores de condición
4 bits 5 bits

Figura 6.6 Registro de estado del Los programas serían aburridos si solo pudieran ejecutarse en el mismo orden cada vez.
programa actual (CPSR) Instrucciones ARM configuradas opcionalmente banderas de condición en función de si el
resultado es negativo, cero, etc. Las instrucciones posteriores se ejecutan condicionalmente, dependiendo
del estado de esos indicadores de condición. Los indicadores de condición ARM, también
Los cinco bits menos
llamados banderas de estado, son negativos NORTE), cero ( Z), llevar ( C),
significativos del CPSR son modo
y desbordamiento V), como se indica en Cuadro 6.2 . Estos indicadores los establece la ALU (consulte la
bits y se describirá en
Sección 5.2.4) y se mantienen en los 4 bits superiores de la configuración de 32 bits. Pro actual
Sección 6.6.3 .
Registro de estado del gramo (CPSR), como se muestra en Figura 6.6 .
La forma más común de establecer los bits de estado es con la comparación ( CMP)
instrucción, que resta el segundo operando fuente del primero y establece los
Otras instrucciones útiles para indicadores de condición según el resultado. Por ejemplo, si los números son
comparar dos valores son CMN, TST, iguales, el resultado será cero y el Z la bandera está puesta. Si el primer
y TEQ. Cada instrucción realiza una número es un valor sin signo mayor o igual que el segundo, la resta producirá
operación, actualiza los indicadores una ejecución y la C la bandera está puesta.
de condición y Las instrucciones posteriores se pueden ejecutar condicionalmente dependiendo
descarta el resultado. CMN del estado de las banderas. La instrucción mnemotécnica va seguida de una condición
(comparar negativo) compara mnemotécnica que indica cuándo ejecutar. Cuadro 6.3 enumera el campo de condición
la primera fuente al negativo de la
de 4 bits ( cond), el mnemónico de condición, el nombre y el estado de los indicadores de
segunda fuente por
condición que dan como resultado la ejecución de la instrucción (CondEx). Por ejemplo,
agregando las dos fuentes. Como
suponga que un programa realiza CMP R4, R5, y luego ADDEQ R1, R2, R3.
se mostrará en Sección 6.4 , Solo
instrucciones ARM
La comparación establece el Z marca si R4 y R5 son iguales, y el ADDEQ
codificar inmediatos positivos. se ejecuta solo si el Z la bandera está puesta. El cond El campo se utilizará en codificaciones de
Entonces, CMN R2, n. ° 20 se usa lenguaje de máquina en Sección 6.4 .
en lugar de CMP R2, # -20.
TST ( test) ANDs los operandos
de origen. Es útil para
comprobar si alguna parte del registro es Cuadro 6.2 Indicadores de condición

cero o distinto de cero. Para


ejemplo, TST R2, # 0xFF
Bandera Nombre Descripción
establecería el Z bandera si el byte bajo de norte Negativo El resultado de la instrucción es negativo, es decir, el bit 31 del
R2 es 0. TEQ ( prueba si es igual) verifica la resultado es 1
equivalencia mediante XOR-ing de las
fuentes. Por lo tanto, la Z La bandera se Z Cero El resultado de la instrucción es cero La

establece cuando son iguales y el norte La


C Llevar instrucción provoca una ejecución La
bandera se establece cuando los signos
son diferentes. V Desbordamiento instrucción provoca un desbordamiento
6.3 Programación 307

Cuadro 6.3 Condición mnemotécnica


Los nemotécnicos de condición difieren

cond Mnemotécnico Nombre CondEx para comparación firmada y no


firmada. Por ejemplo,
0000 Ecualizador Igual Z ARM proporciona dos formas de
mayor o igual
0001 nordeste No es igual Z
comparación: HS (CS) se utiliza para
0010 CS / HS Carry set / unsigned superior o igual C números sin signo y GE para
firmados. Para números sin firmar, A
0011 CC / LO Carry clear / unsigned inferior C - B producirá un llevar a cabo C) Cuándo
A ≥ B. Para números firmados, A - B hará
0100 MI Menos / negativo norte
norte y V ambos 0 o ambos 1
0101 PL Ajuste de desbordamiento / norte cuando A ≥ B. Figura 6.7

0110 VS desbordamiento positivo / positivo o cero V destaca la diferencia


entre las comparaciones
0111 VC Sin desbordamiento / desbordamiento V
de HS y GE con dos
1000 HOLA claro Sin firmar más alto ZC ejemplos que utilizan números de 4
bits para facilitar la interpretación.
1001 LS Sin signo menor o igual ZOC

1010 GE Firmado mayor o igual Firmado norte ⊕ V

1011 LT menor que norte ⊕ V


Sin firmar firmado

1100 GT Firmado mayor que Z D norte ⊕ V Þ A = 1001 2 A=9 A = –7


B = 0010 2 B=2 B=2
1101 LE Firmado menor o igual que Z O D norte ⊕ V Þ
A - B: 1001 NZCV = 0011 2
1110 AL (o ninguno) Siempre / incondicional Ignorado + 1110 HS: CIERTO

(a) 1 0111 GE: FALSO

Otras instrucciones de procesamiento de datos establecerán los indicadores de condición Sin firmar firmado

cuando la instrucción mnemotécnica va seguida de " S. " Por ejemplo, SUBS R2, R3, R7 A = 0101 2 A=5 A=5

restará R7 de R3, pondrá el resultado en R2 y establecerá los indicadores de condición. La Tabla B = 1101 2 B = 13 B = –3

B.5 en el Apéndice B resume qué indicadores de condición están influenciados por cada A - B: 0101 NZCV = 1001 2
instrucción. Todas las instrucciones de procesamiento de datos afectarán norte y + 0011 HS: FALSO
Z banderas basadas en si el resultado es cero o si tiene el bit más significativo (B) 1000 GE: CIERTO
establecido. AGREGA y SUBS también influye V y C, y cambia la influencia C.
Figura 6.7 Comparación firmada y no
El ejemplo de código 6.10 muestra instrucciones que se ejecutan
firmada: HS frente a GE
condicionalmente. La primera instrucción, CMP R2, R3, se ejecuta incondicionalmente
y establece las banderas de condición. Las instrucciones restantes se ejecutan
condicionalmente, dependiendo de los valores de las banderas de condición.
Suponga que R2 y R3 contienen los valores 0x80000000 y 0x00000001. La
comparación calcula R2 - R3 = 0x80000000 - 0x00000001 = 0x80000000 + 0xFFFFFFFF
= 0x7FFFFFFF con un llevar a cabo ( C = 1). Las fuentes tenían signos opuestos y el
signo del resultado difiere del signo de la primera fuente, por lo que el resultado se
desborda ( V = 1). Las banderas restantes ( norte y Z) son 0. ANDHS ejecuta
308 CAPITULO SEIS Arquitectura

Ejemplo de código 6.10 EJECUCIÓN CONDICIONAL

Código de ensamblaje ARM

CMP R2, R3
ADDEQ R4, R5, # 78 ANDHS
R7, R8, R9 ORRMI R10, R11,
R12 EORLT R12, R7, R10

porque C = 1. EORLT se ejecuta porque norte es 0 y V es 1 (ver Cuadro 6.3 ).


Intuitivamente ANDHS y EORLT ejecutar porque R2 ≥ R3 (sin firmar) y R2
< R3 (firmado), respectivamente. ADDEQ y ORRMI no ejecutar porque el resultado de
R2 - R3 no es cero (es decir, R2 ≠ R3) o negativo.

6. 3. 3 Derivación

Una ventaja de una computadora sobre una calculadora es su capacidad para tomar decisiones.
Una computadora realiza diferentes tareas dependiendo de la entrada. Por ejemplo, las
declaraciones if / else, las declaraciones switch / case, los bucles while y los bucles for ejecutan
código de forma condicional dependiendo de alguna prueba.
Una forma de tomar decisiones es utilizar la ejecución condicional para ignorar
ciertas instrucciones. Esto funciona bien para sentencias if simples en las que se ignora
una pequeña cantidad de instrucciones, pero es un desperdicio para sentencias if con
muchas instrucciones en el cuerpo, y es insuficiente para manejar bucles. Por lo tanto,
ARM y la mayoría de las otras arquitecturas utilizan instrucciones de rama
para omitir secciones de código o repetir código.
Un programa generalmente se ejecuta en secuencia, con el contador de programa (PC)
incrementándose en 4 después de cada instrucción para apuntar a la siguiente instrucción.
(Recuerde que las instrucciones tienen 4 bytes de longitud y ARM es una arquitectura con
dirección única). Las instrucciones de rama cambian el contador del programa. ARM incluye dos
tipos de ramas: una simple rama ( B) y rama y enlace LICENCIADO EN DERECHO). licenciado en
Derecho se utiliza para llamadas a funciones y se analiza en Sección 6.3.7 . Al igual que otras
instrucciones ARM, las ramas pueden ser incondicionales o condicionales. Las ramas también
se llaman saltos en algunas arquitecturas.
El ejemplo de código 6.11 muestra la bifurcación incondicional usando la
instrucción de bifurcación B. Cuando el código llega al B OBJETIVO instrucción, la
rama es tomado. Es decir, la siguiente instrucción ejecutada es la SUB
instrucción justo después de la etiqueta llamado OBJETIVO.
El código de ensamblaje usa etiquetas para indicar ubicaciones de instrucciones en el
programa. Cuando el código de ensamblaje se traduce en código de máquina, estas etiquetas
se traducen en direcciones de instrucciones (ver Sección 6.4.3 ). Las etiquetas de ensamblaje
ARM no pueden ser palabras reservadas, como mnemotécnicos de instrucción. La mayoría de
los programadores sangran sus instrucciones, pero no las etiquetas, para ayudar
6.3 Programación 309

Ejemplo de código 6.11 RAMIFICACIÓN INCONDICIONAL

Código de ensamblaje ARM

AÑADIR R1, R2, # 17 ; R1 = R2 + 17


B OBJETIVO ; bifurcar a TARGET; sin
ORR R1, R1, R3 ejecutar
Y R3, R1, # 0xFF; sin ejecutar

OBJETIVO
SUB R1, R1, # 78 ; R1 = R1 - 78

Ejemplo de código 6.12 RAMIFICACIÓN CONDICIONAL

Código de ensamblaje ARM

MOV R0, n.º 4 ; R0 = 4


AÑADIR R1, R0, R0 ; R1 = R0 + R0 = 8
CMP R0, R1 ; establecer banderas basadas en R0 - R1 = - 4. NZCV = 1000;
BEQ ALLÍ rama no tomada (Z! = 1)
ORR R1, R1, n.º 1 ; R1 = R1 O 1 = 9

ALLÍ
AÑADIR R1, R1, nº 78; R1 = R1 + 78 = 87

hacer que las etiquetas se destaquen. El compilador ARM hace que esto sea un requisito: las
etiquetas no deben tener sangría y las instrucciones deben ir precedidas de espacios en blanco.
Algunos compiladores, incluido GCC, requieren dos puntos después de la etiqueta.
Las instrucciones de bifurcación se pueden ejecutar condicionalmente según los
mnemónicos de condición enumerados en Cuadro 6.3 . El ejemplo de código 6.12 ilustra
el uso de BEQ, ramificación dependiente de la igualdad Z = 1). Cuando el código llega al BEQ instrucción,
la Z el indicador de condición es 0 (es decir, R0 ≠ R1), entonces la rama es no tomado. Es
decir, la siguiente instrucción ejecutada es la
ORR instrucción.

6. 3. 4 Declaraciones condicionales

Las declaraciones if, if / else y switch / case son declaraciones condicionales comúnmente
utilizadas en lenguajes de alto nivel. Cada uno de ellos ejecuta condicionalmente un
cuadra de código que consta de una o más declaraciones. Esta sección muestra
cómo traducir estas construcciones de alto nivel al lenguaje ensamblador ARM.

si declaraciones
Una sentencia if ejecuta un bloque de código, el si bloque, solo cuando se cumple una
condición. El ejemplo de código 6.13 muestra cómo traducir una instrucción if en código
ensamblador ARM.
310 CAPITULO SEIS Arquitectura

Ejemplo de código 6.13 SI DECLARACIÓN

Código de alto nivel Código de ensamblaje ARM

si (manzanas == naranjas) ; R0 = manzanas, R1 = naranjas, R2 = f, R3 = i


f = i + 1; CMP R0, R1 ; manzanas == naranjas?
BNE L1 ; si no es igual, omite el bloque
AÑADIR R2, R3, # 1 ; si bloque: f = i + 1
f = f - I; L1
SUB R2, R2, R3 ;f=f-I

El código ensamblador para la instrucción if prueba la condición opuesta a la del


Recuerde que! = Es una comparación
código de alto nivel. Ejemplo 6.13 de InCode, el código de alto nivel prueba para
de desigualdad y == es una
manzanas == naranjas. El código ensamblador prueba para manzanas! = naranjas
comparación de igualdad en el código
de alto nivel.
usando BNE para omitir el bloque if si la condición es no satisfecho. De lo contrario,
manzanas == naranjas, no se toma la rama y se ejecuta el bloque if.
Debido a que cualquier instrucción se puede ejecutar condicionalmente, el código ensamblador
ARM para el ejemplo de código 6.13 también podría escribirse de manera más compacta como se
muestra a continuación.

CMP R0, R1 ; manzanas == naranjas?


ADDEQ R2, R3, # 1 ; f = i + 1 en igualdad (es decir, Z = 1); f = f - I
SUB R2, R2, R3

Esta solución con ejecución condicional es más corta y también más


rápida porque implica una instrucción menos. Además, veremos en la Sección
7.5.3 que las ramas a veces introducen un retraso adicional, mientras que la
ejecución condicional siempre es rápida. Este ejemplo muestra el poder de la
ejecución condicional en la arquitectura ARM.
En general, cuando un bloque de código tiene una sola instrucción, es
mejor utilizar la ejecución condicional en lugar de ramificarla. A medida que el
bloque se alarga, la rama se vuelve valiosa porque evita perder tiempo
buscando instrucciones que no se ejecutarán.

declaraciones if / else
Las sentencias if / else ejecutan uno de los dos bloques de código dependiendo de una
condición. Cuando se cumple la condición de la sentencia if, la si bloque es ejecutado. De
lo contrario, el otro bloque es ejecutado. El ejemplo de código 6.14 muestra un ejemplo
de declaración if / else.
Al igual que las declaraciones if, el código ensamblador if / else prueba la condición
opuesta a la del código de alto nivel. En el ejemplo de código 6.14, el código de alto nivel
prueba para manzanas == naranjas, y las pruebas de código ensamblador para
manzanas! = naranjas. Si esa condición opuesta es VERDADERA, BNE omite el
bloque if y ejecuta el bloque else. De lo contrario, el bloque if se ejecuta y
termina con una rama incondicional ( B) pasado el bloque else.
6.3 Programación 311

Ejemplo de código 6.14 DECLARACIÓN IF / ELSE

Código de alto nivel Código de ensamblaje ARM

si (manzanas == naranjas) ; R0 = manzanas, R1 = naranjas, R2 = f, R3 = i


f = i + 1; CMP R0, R1 ; manzanas == naranjas?
BNE L1 ; si no es igual, omite el bloque
AÑADIR R2, R3, # 1 ; si bloque: f = i + 1
B L2 ; saltar otro bloque
demás L1
f = f - I; SUB R2, R2, R3 ; demás bloque: f = f - I
L2

Nuevamente, debido a que cualquier instrucción se puede ejecutar condicionalmente y debido a


que las instrucciones dentro del bloque if no cambian los indicadores de condición, el código
ensamblador ARM para el ejemplo de código 6.14 también podría escribirse de manera mucho más
sucinta como:

CMP R0, R1 ; manzanas == naranjas?


ADDEQ R2, R3, # 1 ; f = i + 1 en igualdad (es decir, Z = 1); f = f - yo no
SUBNE R2, R2, R3 es igual (es decir, Z = 0)

Declaraciones switch / case *


Las sentencias switch / case ejecutan uno de varios bloques de código dependiendo de las
condiciones. Si no se cumplen las condiciones, el bloque predeterminado es ejecutado. Un
enunciado de caso es equivalente a una serie de anidado declaraciones if / else. El ejemplo de
código 6.15 muestra dos fragmentos de código de alto nivel con el mismo

Ejemplo de código 6.15 INTERRUPTOR / DECLARACIÓN DE CASO

Código de alto nivel Código de ensamblaje ARM

boton interruptor) { ; R0 = botón, R1 = amt


caso 1: amt = 20; descanso; CMP R0, n.º 1 ; es el botón 1?
MOVEQ R1, n.º 20 ; amt = 20 si el botón es 1;
BEQ HECHO descanso

caso 2: amt = 50; descanso; CMP R0, n.º 2 ; es el botón 2?


MOVEQ R1, n.º 50 ; amt = 50 si el botón es 2;
BEQ HECHO descanso

caso 3: amt = 100; descanso; CMP R0, n.º 3 ; es el botón 3?


MOVEQ R1, n.o 100 ; amt = 100 si el botón es 3;
BEQ HECHO descanso
predeterminado: amt = 0;
} MOV R1, # 0 ; amt predeterminado = 0
// función equivalente usando // HECHO
sentencias if / else
si (botón == 1) amt = 20;
si no (botón == 2) amt = 50; si no (botón ==
3) amt = 100; demás
amt = 0;
312 CAPITULO SEIS Arquitectura

funcionalidad: calculan si dispensar $ 20, $ 50 o $ 100 de un cajero automático


(cajero automático) dependiendo del botón presionado. La implementación de
ARMassembly es la misma para ambos fragmentos de código de alto nivel.

6. 3. 5 Volviéndose loco

Los bucles ejecutan repetidamente un bloque de código según una condición. Los
bucles while y for son construcciones de bucles comunes utilizadas por los
lenguajes de alto nivel. Esta sección muestra cómo traducirlos al lenguaje
ensamblador ARM, aprovechando la ramificación condicional.

while Loops
Los bucles while ejecutan repetidamente un bloque de código hasta que se cumple una condición. no
El En t El tipo de datos en C se refiere a
reunió. El ciclo while en el ejemplo de código 6.16 determina el valor de X tal
una palabra de datos que representa
que 2 x = 128. Se ejecuta siete veces, hasta pow = 128.
dos ' s complemento entero. ARM usa
Al igual que las declaraciones if / else, el código ensamblador para bucles
palabras de 32 bits, por lo que un
En t representa un número en el
while prueba la condición opuesta a la del código de alto nivel. Si la condición
rango [ - 2 31, 2 31 - 1]. opuesta es VERDADERA (en este caso, R0 == 128), el ciclo while finaliza. Si no
(R0 ≠ 128), la rama no se toma y el cuerpo del bucle se ejecuta.

Ejemplo de código 6.16 MIENTRAS LOOP

Código de alto nivel Código de ensamblaje ARM

int pow = 1; ; R0 = potencia, R1 = x


int x = 0; MOV R0, n.º 1 ; pow = 1
MOV R1, # 0 ;x=0

while (pow! = 128) { TIEMPO


pow = pow * 2; CMP R0, # 128 ; pow! = 128?
x = x + 1; BEQ HECHO ; si pow == 128, salir del ciclo
} LSL R0, R0, nº 1; pow = pow * 2 AÑADIR
R1, R1, # 1; x = x + 1 B
TIEMPO ; repetir bucle
HECHO

En el ejemplo de código 6.16, el ciclo while compara pow a 128 y sale del bucle si es igual. De lo
contrario se duplica pow usando un desplazamiento a la izquierda), incrementos X,
y se ramifica de nuevo al inicio del ciclo while.

para bucles
Es muy común inicializar una variable antes de un ciclo while, verificar esa
variable en la condición del ciclo y cambiar esa variable cada vez a través del
ciclo while. Los bucles for son una práctica abreviada que combina la
inicialización, la verificación de condición y el cambio de variable en un solo
lugar. El formato del bucle for es:

for (inicialización; condición; operación de bucle)


declaración
6.3 Programación 313

Ejemplo de código 6.17 EN BUCLE

Código de alto nivel Código de ensamblaje ARM

int i; ; R0 = yo, R1 = suma


int suma = 0; MOV R1, # 0 ; suma = 0
MOV R0, # 0 ;i=0 inicialización de bucle

para (i = 0; i <10; i = i + 1) { POR


suma = suma + i; CMP R0, # 10 ; yo <10? comprobar condición
} BGE HECHO ; si (i> = 10) bucle de salida
AÑADIR R1, R1, R0 ; suma = suma + i cuerpo del bucle;
AÑADIR R0, R0, # 1 yo = yo + 1 operación de bucle
B POR ; repetir bucle
HECHO

El código de inicialización se ejecuta antes de que comience el ciclo for. La


condición se prueba al comienzo de cada ciclo. Si no se cumple la condición, el
bucle sale. La operación de bucle se ejecuta al final de cada bucle.
El ejemplo de código 6.17 suma los números del 0 al 9. La variable de ciclo, en
este caso I, se inicializa a 0 y se incrementa al final de cada iteración del ciclo. El
bucle for se ejecuta siempre que I es menor que 10. Tenga en cuenta que este
ejemplo también ilustra comparaciones relativas. El bucle verifica la condición <para
continuar, por lo que el código ensamblador verifica la condición opuesta,> =, para
salir del bucle.
Los bucles son especialmente útiles para acceder a grandes cantidades de datos similares
almacenados en la memoria, que se analiza a continuación.

6. 3. 6 Memoria

Para facilitar el almacenamiento y el acceso, los datos similares se pueden agrupar en un formación.
Una matriz almacena su contenido en direcciones de datos secuenciales en la memoria.
Cada elemento de la matriz se identifica por un número llamado su índice. El número de
elementos de la matriz se denomina longitud de la matriz.
Figura 6.8 muestra una matriz de 200 elementos de puntuaciones almacenadas en la Dirección Datos

memoria. El ejemplo de código 6.18 es un algoritmo de inflación de calificaciones que agrega 1400031C puntuaciones [199]
10 puntos a cada una de las calificaciones. Tenga en cuenta que no se muestra el código para 14000318 puntuaciones [198]
inicializar la matriz de puntuaciones. El índice de la matriz es una variable ( I) en lugar de una
constante, por lo que debemos multiplicarlo por 4 antes de agregarlo a la dirección base.
ARM puede escala ( multiplique) el índice, agréguelo a la dirección base y 14000004 puntuaciones [1]

cargue desde la memoria en una sola instrucción. En vez de LSL y LDR 14000000 puntuaciones [0]

secuencia de instrucciones en el ejemplo de código 6.18, podemos usar una sola instrucción:
Memoria principal
LDR R3, [R0, R1, LSL # 2]
Figura 6.8 Retención de memoria
R1 se escala (se desplaza a la izquierda en dos) y luego se agrega a la dirección base (R0). Por puntuaciones [200] comenzando en la

tanto, la dirección de memoria es R0 + (R1 × 4). dirección base 0x14000000


314 CAPITULO SEIS Arquitectura

Ejemplo de código 6.18 ACCESO A ARRAYS UTILIZANDO UN FOR LOOP

Código de alto nivel Código de ensamblaje ARM

int i; ; R0 = dirección base de la matriz, R1 = i;


int puntuaciones [200]; código de inicialización ...
... MOV R0, # 0x14000000 ; R0 = dirección base
MOV R1, # 0 ;i=0

CÍRCULO

para (i = 0; i <200; i = i + 1) CMP R1, # 200 ; yo <200?


BGE L3 ; si yo ≥ 200, bucle de salida; R2
LSL R2, R1, n.º 2 = yo * 4
puntuaciones [i] = puntuaciones [i] + 10; LDR R3, [R0, R2] ; R3 = puntuaciones [i]
AÑADIR R3, R3, # 10 ; R3 = puntuaciones [i] + 10
STR R3, [R0, R2] ; puntuaciones [i] = puntuaciones [i] +
AÑADIR R1, R1, # 1 10; yo = yo + 1
B CÍRCULO ; repetir bucle
L3

Además de escalar el registro de índice, ARM proporciona direccionamiento


compensado, preindexado y posindexado para permitir un código denso y eficiente
para accesos a matrices y llamadas a funciones. Cuadro 6.4 da ejemplos de cada
modo de indexación. En cada caso, el registro base es R1 y el desplazamiento es R2.
El desplazamiento se puede restar escribiendo - R2. El desplazamiento también
puede ser inmediato en el rango de 0 - 4095 que se pueden sumar (p. Ej., # 20) o
restar (p. Ej., # - 20).
Direccionamiento compensado calcula la dirección como registro base ± el
desplazamiento; el registro base no se modifica. Direccionamiento preindexado calcula la
dirección como registro base ± el desplazamiento y actualiza el registro base a esta
nueva dirección. Direccionamiento posindexado calcula la dirección solo como el registro
base y luego, después de acceder a la memoria, el registro base se actualiza al registro
base ± el desplazamiento. Hemos visto muchos ejemplos de modo de indexación de
compensación. El ejemplo de código 6.19 muestra el bucle for del ejemplo de código 6.18
reescrito para usar post-indexación, eliminando la AGREGAR
para incrementar I.

Cuadro 6.4 Modos de indexación ARM

Modo Ensamblaje del BRAZO Dirección Registro base

Compensar LDR R0, [R1, R2] R1 + R2 Sin alterar

Pre-índice LDR R0, [R1, R2]! R1 + R2 R1 = R1 + R2

Post-índice LDR R0, [R1], R2 R1 R1 = R1 + R2


Producido con
Produced unaa versión
with de prueba
Trial Version ofde PDFAnnotator
PDF Annotator - -www.PDFAnnotator.com
www.PDFAnnotator.com

6.3 Programación 315

Ejemplo de código 6.19 PARA BUCLE USANDO POST-INDEXING

Código de alto nivel Código de ensamblaje ARM

int i; ; R0 = dirección base de la matriz;


int puntuaciones [200]; código de inicialización ...
... MOV R0, # 0x14000000 ; R0 = dirección base
AÑADIR R1, R0, # 800 ; R1 = dirección base + (200 * 4)

para (i = 0; i <200; i = i + 1) CÍRCULO

puntuaciones [i] = puntuaciones [i] + 10; CMP R0, R1 ; alcanzado el final de la matriz?
BGE L3 ; si es así, salir del ciclo; R2 =
LDR R2, [R0] puntuaciones [i]
AGREGAR R2, R2, # 10 ; R2 = puntuaciones [i] + 10
STR R2, [R0], n.º 4 ; puntuaciones [i] = puntuaciones [i] +
10; entonces R0 = R0 + 4
B CÍRCULO ; repetir bucle
L3

Bytes y caracteres
Números en el rango [ - 128, 127] se pueden almacenar en un solo byte en lugar de
una palabra completa. Debido a que hay mucho menos de 256 caracteres en un
teclado en inglés, los caracteres en inglés a menudo se representan mediante
Otros lenguajes de programación,
bytes. El lenguaje C usa el tipo carbonizarse para representar un byte o carácter.
como Java, utilizan diferentes

codificaciones de caracteres, la mayoría


Las primeras computadoras carecían de un mapeo estándar entre bytes y
notablemente Unicode. Unicode usa 16
caracteres en inglés, por lo que el intercambio de texto entre computadoras bits para representar cada
era difícil. En 1963, la American Standards Association publicó carácter, por lo que admite acentos,
la Código estándar americano para el intercambio de información (ASCII), diéresis e idiomas asiáticos. Para
que asigna a cada carácter de texto un valor de byte único. Cuadro 6.5 más información, ver
muestra estas codificaciones de caracteres para caracteres imprimibles. Los valores www.unicode.org .
ASCII se dan en hexadecimal. Las letras minúsculas y mayúsculas difieren en 0x20
(32).
ARM proporciona el byte de carga ( LDRB), cargar byte firmado LDRSB), y almacenar
LDRH, LDRSH, y STRH están
byte STRB) para acceder a bytes individuales en la memoria. LDRB cero-extiende el byte,
similar, pero acceda a 16 bits
mientras que LDRSB sign-extiende el byte para llenar todo el registro de 32 bits.
medias palabras.
STRB almacena el byte menos significativo del registro de 32 bits en la dirección de
byte especificada en la memoria. Los tres se ilustran en Figura 6.9 , con

Memoria Registros
Dirección de byte 3 2 1 0 R1 00 00 00 8C LDRB R1, [R4, # 2]
Datos F7 8C 42 03 Figura 6.9 Instrucciones para cargar y
R2 FF FF FF8C LDRSB R2, [R4, n.º 2] almacenar bytes

R3 xx xx xx 9B STRB R3, [R4, # 3]


Produced with a Trial Version of PDF Annotator - www.PDFAnnotator.com

316 CAPITULO SEIS Arquitectura

Cuadro 6.5 Codificaciones ASCII

Los códigos ASCII se desarrollaron a


# Carbonizarse # Carbonizarse # Carbonizarse # Carbonizarse # Carbonizarse # Carbonizarse
partir de formas anteriores de
codificación de caracteres. A partir de 20 espacio 30 0 40 @ 50 PAG 60 ' 70 pag
1838, se utilizaron máquinas de telégrafo
Código Morse, una serie de puntos 21 ! 31 1 41 A 51 Q 61 a 71 q
(.) Y guiones ( -), para representar
22 " 32 2 42 B 52 R 62 B 72 r
personajes. Por ejemplo, las letras A,
B, C y D se representaron como -, -…, 23 # 33 3 43 C 53 S 63 C 73 s
-. -. ,
y - ‥, respectivamente. El 24 PS 34 4 44 D 54 T 64 D 74 t
número de puntos y rayas
25 % 35 5 45 mi 55 U mi
sesenta y cinco 75 tu
variaba con cada letra. Para
eficiencia, letras comunes 26 Y 36 6 46 F 56 V 66 F 76 v
utiliza códigos más cortos.
En 1874, Jean-Maurice- 27 ' 37 7 47 GRAMO 57 W 67 gramo 77 w
Emile Baudot inventó un código de
28 ( 38 8 48 H 58 X 68 h 78 X
5 bits llamado código Baudot. Por
ejemplo, A, B, C y D se 29 ) 39 9 49 I 59 Y 69 I 79 y
representaron como 00011,
11001, 01110 y 01001. Sin 2A * 3A : 4A J 5A Z 6A j 7A z
embargo, los 32 posibles
2B + 3B ; 4B K 5B [ 6B k 7B {
las codificaciones de este código de 5 bits
no eran suficientes para todos los
2C , 3C < 4C L 5C \ 6C l 7C |
caracteres en inglés, pero sí la codificación
de 8 bits. Así, como electrónica 2D - 3D = 4D METRO 5D ] 6D metro 7D }
la comunicación se convirtió
2E . 3E > 4E norte 5E ^ 6E norte 7E ~
La codificación ASCII de 8 bits
prevalente surgió como estándar.
2F / 3F ? 4F O 5F _ 6F o

la dirección base R4 es 0. LDRB carga el byte en la dirección de memoria 2 en el


byte menos significativo de R1 y llena los bits de registro restantes con 0. LDRSB
carga este byte en R2 y el signo extiende el byte a los 24 bits superiores del
registro. STRB almacena el byte menos significativo de R3 (0x9B) en el byte 3 de
memoria; reemplaza 0xF7 con 0x9B. Los bytes más significativos de R3 se
ignoran.
Una serie de caracteres se llama cuerda. Las cadenas tienen una longitud variable,
por lo que los lenguajes de programación deben proporcionar una forma de determinar
la longitud o el final de la cadena. En C, el carácter nulo (0x00) significa el final de una
cadena. Por ejemplo, Figura 6.10 muestra la cuerda " ¡Hola! "( 0x48 65 6C 6C 6F 21 00)
almacenado en la memoria. La cadena tiene siete bytes de longitud.
6.3 Programación 317

Ejemplo 6.2 USANDO LDRB Y STRB PARA ACCEDER A UNA MATRIZ DE CARACTERES

El siguiente código de alto nivel convierte una matriz de caracteres de 10 entradas de minúsculas a mayúsculas
restando 32 de cada entrada de la matriz. Traducirlo al lenguaje ensamblador ARM. Recuerde que la diferencia
de direcciones entre los elementos de la matriz ahora es de 1 byte, no de 4 bytes. Suponga que R0 ya tiene la
dirección base de
chararray.

// código de alto nivel


// chararray [10] declarado e inicializado anteriormente int i;

para (i = 0; i <10; i = i + 1)
chararray [i] = chararray [i] - 32;

Solución:
; Código de ensamblaje ARM
; R0 = dirección base de chararray (inicializada antes), R1 = i
MOV R1, # 0 ;i=0
CÍRCULO CMP R1, n.º 10 ; yo <10?
BGE HECHO ; si (i> = 10), salir del ciclo
LDRB R2, [R0, R1] ; R2 = mem [R0 + R1] = chararray [i]
SUB R2, R2, # 32 ; R2 = chararray [i] - 32
STRB R2, [R0, R1] ; chararray [i] = R2
AÑADIR R1, R1, # 1 ; yo = yo + 1
B CÍRCULO ; repetir bucle
HECHO

y se extiende desde la dirección 0x1522FFF0 hasta 0x1522FFF6. El primer Dirección de palabra Datos

carácter de la cadena (H = 0x48) se almacena en la dirección de byte más baja


(0x1522FFF0).
1522FFF4 00 21 6F

6. 3. 7 Llamadas a funciones 1522FFF0 6C 6C 48


sesenta y cinco

Soporte de idiomas de alto nivel funciones ( también llamado procedimientos o subrutinas)


Byte 3 Byte 0
reutilizar código común y hacer un programa más modular y legible. Las funciones
Memoria
tienen entradas, llamadas argumentos, y una salida, llamada valor de retorno. Las
funciones deben calcular el valor de retorno y no causar otros efectos secundarios Figura 6.10 La cuerda " ¡Hola! "
no deseados. almacenado en la memoria

Cuando una función llama a otra, la función que llama, la llamador,


y la función llamada, la llamar, Debe acordar dónde colocar los argumentos y el
valor de retorno. En ARM, la persona que llama coloca convencionalmente hasta
cuatro argumentos en los registros R0 - R3 antes de realizar la llamada a la función,
318 CAPITULO SEIS Arquitectura

y el destinatario coloca el valor de retorno en el registro R0 antes de finalizar. Siguiendo esta


convención, ambas funciones saben dónde encontrar los argumentos y el valor de retorno,
incluso si la persona que llama y la persona que llama fueron escritas por diferentes personas.

La persona que llama no debe interferir con el comportamiento de la persona que llama. Esto
significa que la persona que llama debe saber a dónde regresar después de que se complete y no debe
pisotear ningún registro o memoria que necesite la persona que llama. La persona que llama almacena
la dirección de retorno en el registro de enlace LR al mismo tiempo que salta al destinatario de la
llamada utilizando la instrucción de bifurcación y enlace ( LICENCIADO EN DERECHO). La persona que
llama no debe sobrescribir ningún estado arquitectónico o memoria del que dependa la persona que
llama. Específicamente, el destinatario debe dejar el registros guardados
(R4 - R11 y LR) y el apilar, una parte de la memoria utilizada para variables
temporales, sin modificar.
Esta sección muestra cómo llamar y regresar desde una función. Muestra cómo las
funciones acceden a los argumentos de entrada y al valor de retorno y cómo usan la pila
para almacenar variables temporales.

Llamadas y devoluciones de funciones

ARM usa la instrucción de bifurcación y enlace ( LICENCIADO EN DERECHO) para llamar a


una función y mueve el registro de enlace a la PC ( MOV PC, LR) para volver de una función.
El ejemplo de código 6.20 muestra el principal función llamando al sencillo función. principal
es la persona que llama, y sencillo es el destinatario. El
sencillo la función se llama sin argumentos de entrada y no genera ningún valor de
retorno; simplemente vuelve a la persona que llama. En el ejemplo de código 6.20,
las direcciones de instrucción se dan a la izquierda de cada instrucción ARM en
hexadecimal.
BL ( rama y enlace) y MOV PC, LR son las dos instrucciones esenciales necesarias para
la llamada y el retorno de una función. licenciado en Derecho realiza dos tareas: almacena
el dirección del remitente de la siguiente instrucción (la instrucción

Ejemplo de código 6.20 LLAMADA DE FUNCIÓN simple

Código de alto nivel Código de ensamblaje ARM

int main () { 0x00008000 PRINCIPAL ...


... ...
sencillo(); 0x00008020 BL SIMPLE ; llamar a la función simple
... ...
}

// void significa que la función no devuelve ningún valor void


simple () { 0x0000902C PC MOV SIMPLE, LR ; regreso
regreso;
}
6.3 Programación 319

después LICENCIADO EN DERECHO) en el registro de enlace (LR), y se bifurca a la instrucción de


Recuerde que PC y LR son nombres
destino.
alternativos para R15 y R14,
En el ejemplo de código 6.20, el principal la función llama al sencillo función ejecutando la respectivamente. ARM es inusual en
instrucción de enlace y bifurcación ( LICENCIADO EN DERECHO). licenciado en Derecho ramas a la que PC es parte del conjunto de
SENCILLO etiqueta y almacena 0x00008024 en LR. El sencillo la función regresa registros, por lo que se puede hacer
inmediatamente al ejecutar la instrucción MOV PC, LR, copiando la dirección de un retorno de función con un MOV
retorno del LR a la PC. El principal la función luego continúa ejecutándose en instrucción. Muchos otros
esta dirección (0x00008024). Los conjuntos de instrucciones mantienen
la PC en un registro especial y usan un
retorno o salto especial
Argumentos de entrada y valores devueltos instrucción para regresar de
El sencillo La función en el ejemplo de código 6.20 no recibe ninguna entrada de la funciones.
función de llamada ( principal) y no devuelve ninguna salida. Por convención ARM, las En estos días, los compiladores ARM

funciones usan R0 - R3 para argumentos de entrada y R0 para el valor de retorno. En hacen un retorno de función usando

BX LR. El BX La instrucción de bifurcación e


el ejemplo de código 6.21, la función diffofsums se llama con cuatro argumentos y
intercambio es como una bifurcación, pero
devuelve un resultado. resultado es una variable local, que elegimos mantener en
también puede hacer la transición entre el
R4.
conjunto de instrucciones ARM estándar y
De acuerdo con la convención ARM, la función de llamada, principal, coloca los
el conjunto de instrucciones Thumb
argumentos de la función de izquierda a derecha en los registros de entrada, R0 - R3. descrito en
La función llamada, diffosums, almacena el valor de retorno en el registro de retorno, Sección 6.7.1 . T su capítulo no ' t
R0. Cuando se llama a una función con más de cuatro argumentos, los argumentos use el pulgar o
de entrada adicionales se colocan en la pila, que discutiremos a continuación. BX instrucciones y, por lo tanto, se
adhiere al ARMv4 MOV PC, LR
método.
Veremos en el capítulo 7 que
tratar el PC como un registro
Ejemplo de código 6.21 LLAMADA A FUNCIÓN CON ARGUMENTOS Y ordinario complica

VALORES DEVUELTOS la implementación del


procesador.

Código de alto nivel Código de ensamblaje ARM

int main () { ; R4 = y
int y; PRINCIPAL El ejemplo de código 6.21 tiene algunos
... ...
errores sutiles. Ejemplos de código
y = sumas difusas (2, 3, 4, 5); MOV R0, n.º 2 ; argumento 0 = 2;
... MOV R1, n.º 3 argumento 1 = 3; 6.22 - 6.25 espectáculo mejorado
} MOV R2, n.º 4 argumento 2 = 4; versiones del programa.
MOV R3, n.º 5 argumento 3 = 5
BL DIFFOFSUMS; función de llamada MOV
R4, R0 ; y = valor devuelto
...

; R4 = resultado
int diffofsums (int f, int g, int h, int i) { DIFFOFSUMS
resultado int; AÑADIR R8, R0, R1 ; R8 = f + g
AÑADIR R9, R2, R3 ; R9 = h + yo
resultado = (f + g) - ( h + i); devolver SUB R4, R8, R9 ; resultado = (f + g) - ( h + i); poner el
resultado; MOV R0, R4 valor de retorno en R0; volver a la
} MOV PC, LR persona que llama
320 CAPITULO SEIS Arquitectura

Dirección Datos La pila


La pila es memoria que se usa para guardar información dentro de una función. La
BEFFFAE8 AB000001 SP pila se expande (usa más memoria) a medida que el procesador necesita más
BEFFFAE4 espacio temporal y se contrae (usa menos memoria) cuando el procesador ya no
BEFFFAE0 necesita las variables almacenadas allí. Antes de explicar cómo las funciones usan la
BEFFFADC pila para almacenar valores temporales, explicamos cómo funciona la pila.

(a) La pila es una cola de último en entrar, primero en salir (LIFO). Como una pila de
platos, el último elemento empujado en la pila (el plato superior) es el primero que se
Dirección Datos
puede estalló apagado. Cada función puede asignar espacio de pila para almacenar
variables locales, pero debe desasignarlo antes de regresar. El parte superior de la pila es
BEFFFAE8 AB000001
el espacio asignado más recientemente. Mientras que una pila de platos crece en el
BEFFFAE4 12345678
espacio, la pila ARM crece en la memoria. La pila se expande a direcciones de memoria
BEFFFAE0 FFEEDDCC SP
más bajas cuando un programa necesita más espacio temporal.
BEFFFADC
Figura 6.11 muestra una imagen de la pila. El puntero de pila, SP (R13), es un
registro ARM ordinario que, por convención, puntos hacia parte superior de la pila. Un
(B) puntero es un nombre elegante para una dirección de memoria. SP apunta a (da la
Memoria
dirección de) datos. Por ejemplo, en Figura 6.11 (a) , el puntero de pila, SP, contiene
Figura 6.11 La pila (a) antes de la el valor de dirección 0XBEFFFAE8 y apunta al valor de datos 0xAB000001.
expansión y (b) después de la expansión de
dos palabras El puntero de pila (SP) comienza en una dirección de memoria alta y disminuye para
expandirse según sea necesario. Figura 6.11 (b) muestra la pila expandiéndose para
Por lo general, la pila se almacena boca
permitir dos palabras de datos más de almacenamiento temporal. Para hacerlo, SP
abajo en la memoria, de modo que la
disminuye en ocho para convertirse en 0xBEFFFAE0. Dos palabras de datos adicionales,
parte superior de la pila es en realidad
0x12345678 y 0xFFEEDDCC, se almacenan temporalmente en la pila.
la dirección más baja y la pila crece
hacia las direcciones de memoria más
Uno de los usos importantes de la pila es guardar y restaurar registros
bajas. Esto se llama pila descendente. ARM que utiliza una función. Recuerde que una función debe calcular un valor de
también permite pilas ascendentes que retorno pero no tener otros efectos secundarios no deseados. En particular, no
crecen hacia direcciones de memoria debe modificar ningún registro además de R0, el que contiene el valor de
más altas. El puntero de la pila retorno. El diffofsums La función en el ejemplo de código 6.21 viola esta regla
normalmente apunta al elemento porque modifica R4, R8 y R9. Si principal había estado usando estos registros
superior de la pila; esto se llama completa antes de la llamada a diffosums, su contenido habría sido dañado por la
pila. ARM también permite
llamada a la función.
Para resolver este problema, una función guarda registros en la pila antes
pilas vacías en el que SP apunta una
de modificarlos, luego los restaura de la pila antes de que regrese. En
palabra más allá de la parte superior de la
concreto, realiza los siguientes pasos:
pila. El brazo Interfaz binaria de aplicación
( ABI) define una forma estándar en la que 1. Hace espacio en la pila para almacenar los valores de uno o más registros.
las funciones pasan variables y usan la pila
para que las bibliotecas desarrolladas por 2. Almacena los valores de los registros en la pila.
diferentes compiladores puedan
3. Ejecuta la función usando los registros.
interoperar. Especifica un descendente 4. Restaura los valores originales de los registros de la pila.
completo stack, que usaremos en
este capítulo. 5. Distribuye espacio en la pila.
6.3 Programación 321

El ejemplo de código 6.22 muestra una versión mejorada de diffofsums que


guarda y restaura R4, R8 y R9. Figura 6.12 muestra la pila antes, durante y
después de una llamada al diffofsums función del ejemplo de código
6.22. La pila comienza en 0xBEF0F0FC. diffofsums deja espacio para tres
palabras en la pila al disminuir el puntero de pila SP en 12. Luego almacena los
valores actuales retenidos en R4, R8 y R9 en el espacio recién asignado. Ejecuta
el resto de la función, cambiando los valores en estos tres registros. Al final de
la función, diffofsums restaura los valores de estos registros de la pila, desasigna
su espacio de pila y regresa. Cuando la función regresa, R0 contiene el
resultado, pero no

Ejemplo de código 6.22 REGISTROS DE GUARDADO DE FUNCIONES EN LA PILA

Código de ensamblaje ARM

; R4 = resultado
DIFFOFSUMS
SUB SP, SP, # 12 ; hacer espacio en la pila para 3 registros; guardar
STR R9, [SP, # 8] R9 en la pila
STR R8, [SP, # 4] ; guardar R8 en la pila;
STR R4, [SP] guardar R4 en la pila

AÑADIR R8, R0, R1 ; R8 = f + g


AÑADIR R9, R2, R3 ; R9 = h + yo
SUB R4, R8, R9 ; resultado = (f + g) - ( h + i); poner el
MOV R0, R4 valor de retorno en R0

LDR R4, [SP] ; restaurar R4 de la pila;


LDR R8, [SP, # 4] restaurar R8 de la pila; restaurar
LDR R9, [SP, # 8] R9 de la pila
AGREGAR SP, SP, # 12 ; desasignar espacio de pila

MOV PC, LR ; volver a la persona que llama

Dirección Datos Dirección Datos Dirección Datos

BEF0F0FC ? SP BEF0F0FC ? BEF0F0FC ? SP

BEF0F0F8 BEF0F0F8 R9 BEF0F0F8


Marco de pila

BEF0F0F4 BEF0F0F4 R8 BEF0F0F4

BEF0F0F0 BEF0F0F0 R4 SP BEF0F0F0

(a) (B) (C)

Figura 6.12 La pila: (a) antes, (b) durante y (c) después de la diffofsums Llamada de función
322 CAPITULO SEIS Arquitectura

no hay otros efectos secundarios: R4, R8, R9 y SP tienen los mismos valores que tenían
antes de la llamada a la función.
El espacio de pila que una función se asigna a sí misma se llama su marco de pila. diffofsums
' El marco de la pila tiene tres palabras de profundidad. El principio de modularidad nos
dice que cada función debe acceder solo a su propio marco de pila, no a los marcos que
pertenecen a otras funciones.

Carga y almacenamiento de varios registros


Guardar y restaurar registros en la pila es una operación tan común que ARM
proporciona las instrucciones Load Multiple y Store Multiple ( LDM y STM)
que están optimizados para este propósito. El ejemplo de código 6.23 reescribe diffofsums
utilizando estas instrucciones. La pila contiene exactamente la misma información que en
el ejemplo anterior, pero el código es mucho más corto.

Ejemplo de código 6.23 GUARDAR Y RESTAURAR MÚLTIPLES REGISTROS

Código de ensamblaje ARM

; R4 = resultado
DIFFOFSUMS
STMFD SP !, {R4, R8, R9} ; presione R4 / 8/9 en la pila descendente completa

AGREGAR R8, R0, R1 ; R8 = f + g


AGREGAR R9, R2, R3 ; R9 = h + yo
SUB R4, R8, R9 ; resultado = (f + g) - ( h + i); poner el
MOV R0, R4 valor de retorno en R0

LDMFD SP !, {R4, R8, R9} MOV ; quitar R4 / 8/9 de la pila descendente completa; volver a la

PC, LR persona que llama

LDM y STM vienen en cuatro sabores para pilas descendentes y ascendentes llenas y
vacías ( FD, ED, FA, EA). El SP! en las instrucciones indica almacenar los datos relativos al
puntero de la pila y actualizar el puntero de la pila después del almacenamiento o la
carga. EMPUJAR y MÚSICA POP son sinónimos de STMFD SP !, {regs}
y LDMFD SP !, {regs}, respectivamente, y son la forma preferida de guardar registros
en la pila descendente completa convencional.

Registros preservados
Los ejemplos de código 6.22 y 6.23 asumen que todos los registros utilizados
(R4, R8 y R9) deben guardarse y restaurarse. Si la función de llamada no usa
esos registros, el esfuerzo para guardarlos y restaurarlos es en vano. Para
evitar este desperdicio, ARM divide los registros en Preservado y
sin conservantes categorías. Los registros conservados incluyen R4 - R11. Los
registros no conservados son R0 - R3 y R12. SP y LR (R13 y R14)
6.3 Programación 323

también debe conservarse. Una función debe guardar y restaurar cualquiera de los
registros preservados que desee usar, pero puede cambiar los registros no preservados
libremente.
El ejemplo de código 6.24 muestra una versión mejorada de diffofsums
eso guarda solo R4 en la pila. También ilustra el preferido EMPUJAR y MÚSICA POP
sinónimos. El código reutiliza los registros de argumentos no preservados R1 y R3 para
contener las sumas intermedias cuando esos argumentos ya no son necesarios.

Ejemplo de código 6.24 REDUCIR EL NÚMERO DE REGISTROS CONSERVADOS EMPUJAR ( y MÚSICA POP) guardar (y
restaurar) registros en la pila en orden de
Código de ensamblaje ARM número de registro de menor a mayor,

; R4 = resultado con el registro con el número más bajo


DIFFOFSUMS colocado en la dirección de memoria más
PRESIONE {R4} ; guardar R4 en la pila;
baja,
AGREGAR R1, R0, R1 R1 = f + g
independientemente del orden indicado en
AGREGAR R3, R2, R3 ; R3 = h + yo
las instrucciones de montaje. Para
SUB R4, R1, R3 ; resultado = (f + g) - ( h + i); poner el
MOV R0, R4 valor de retorno en R0; sacar R4 de la
ejemplo, PRESIONE {R8, R1, R3}

POP {R4} pila almacenará R1 en la dirección de

MOV PC, LR ; volver a la persona que llama memoria más baja, luego R3 y finalmente
R8 en las siguientes direcciones de
memoria más altas de la pila.

Recuerde que cuando una función llama a otra, la primera es la persona que llama y
la última es el destinatario. El destinatario de la llamada debe guardar y restaurar los
registros conservados que desee utilizar. El destinatario puede cambiar cualquiera de los
registros no conservados. Por lo tanto, si la persona que llama tiene datos activos en un
registro no conservado, la persona que llama necesita guardar ese registro no
preservado antes de realizar la llamada a la función y luego debe restaurarlo. Por estas
razones, los registros conservados también se denominan llamar-guardar, y los registros
no conservados se denominan llamada-guardar.
Cuadro 6.6 resume qué registros se conservan. R4 - Los R11 generalmente se
usan para contener variables locales dentro de una función, por lo que deben
guardarse. LR también debe guardarse, para que la función sepa dónde regresar.

Cuadro 6.6 Registros preservados y no preservados

Preservado Sin conservantes

Registros guardados: R4 - R11 Registro temporal: R12

Puntero de pila: SP (R13) Registros de argumentos: R0 - R3

Dirección de devolución: LR (R14) Registro de estado actual del programa

Apilar encima del puntero de la pila Pila debajo del puntero de pila
324 CAPITULO SEIS Arquitectura

R0 - R3 y R12 se utilizan para mantener resultados temporales. Estos cálculos


La convención de qué registros se
generalmente se completan antes de que se realice una llamada a una función, por lo
conservan o no se conservan
forma parte de la que no se conservan y es raro que la persona que llama necesite guardarlos.
Estándar de llamada a procedimiento R0 - Los R3 a menudo se sobrescriben en el proceso de llamar a una función. Por lo tanto,
para la arquitectura ARM, en lugar de la persona que llama debe guardarlos si la persona que llama depende de cualquiera de sus
la arquitectura en sí. Llamada a propios argumentos después de que regrese una función llamada. Ciertamente, R0 no debe
procedimiento alternativo conservarse, porque el destinatario de la llamada devuelve su resultado en este registro.
existen normas. Recuerde que el Registro de estado del programa actual (CPSR) contiene los indicadores de
condición. No se conserva en las llamadas a funciones.
La pila sobre el puntero de la pila se conserva automáticamente siempre que el
destinatario de la llamada no escriba en direcciones de memoria por encima de SP.
De esta forma, no modifica el marco de pila de ninguna otra función. El puntero de
la pila en sí mismo se conserva, porque el destinatario desasigna su marco de pila
antes de regresar agregando la misma cantidad que resta de SP al comienzo de la
función.
El lector astuto o un compilador optimizador puede notar que la variable local resultado
se devuelve inmediatamente sin ser utilizado para nada más. Por lo tanto, podemos
eliminar la variable y simplemente almacenarla en el registro de retorno R0,
eliminando la necesidad de presionar y abrir R4 y mover resultado de R4 a R0. El
ejemplo de código 6.25 muestra esto aún más optimizado diffofsums.

Llamadas a funciones sin hojas

Una función que no llama a otros se llama función de la hoja; diffofsums


es un ejemplo. Una función que llama a otros se llama función sin hojas.
Como se mencionó, las funciones que no son de hoja son algo más complicadas
porque pueden necesitar guardar registros no preservados en la pila antes de
llamar a otra función y luego restaurar esos registros. Específicamente:

Regla para guardar llamadas: Antes de una llamada de función, la persona que llama debe
guardar los registros no conservados (R0 - R3 y R12) que necesita después de la llamada. Después
de la llamada, debe restaurar estos registros antes de usarlos.

Regla para guardar llamadas: Antes de que una persona que llama perturbe cualquiera de los
registros conservados (R4 - R11 y LR), debe guardar los registros. Antes de que regrese, debe
restaurar estos registros.

Ejemplo de código 6.25 OPTIMIZADO diffofsums LLAMADA DE FUNCIÓN

Código de ensamblaje ARM

DIFFOFSUMS
AGREGAR R1, R0, R1 ; R1 = f + g
AGREGAR R3, R2, R3 ; R3 = h + yo
SUB R0, R1, R3 ; volver (f + g) - ( h + i); volver a la
MOV PC, LR persona que llama
6.3 Programación 325

El ejemplo de código 6.26 demuestra una función sin hojas f1 y una función de
hoja f2 incluyendo todo el guardado y conservación de registros necesarios.
Suponer f1 mantiene I en R4 y X en R5. f2 mantiene r en R4. f1 utiliza registros
preservados R4, R5 y LR, por lo que inicialmente los coloca en la pila de acuerdo con Una función no hoja sobrescribe LR

la regla de guardado del destinatario. Utiliza R12 para mantener el resultado cuando llama a otra función usando LICENCIADO

intermedio ( a - B) por lo que no necesita conservar otro registro para este cálculo. EN DERECHO. Por lo tanto, una función
que no sea de hoja siempre debe guardar
Antes de llamar f2, f1 inserta R0 y R1 en la pila de acuerdo con la regla de guardado
LR en su pila y restaurarla antes de
de la persona que llama porque estos son registros no preservados que f2 podría
regresar.
cambiar y eso f1 todavía necesitará después de la llamada. Aunque R12 es también
un registro no preservado que f2 podría sobrescribir,
f1 ya no necesita R12 y no ' t tengo que salvarlo. f1 luego pasa el argumento a f2 en
R0, realiza la llamada a la función y utiliza el resultado en R0. f1 luego restaura
R0 y R1 porque todavía los necesita. Cuándo f1
termina, pone el valor de retorno en R0, restaura los registros preservados R4,
R5 y LR, y regresa. f2 guarda y restaura R4 de acuerdo con la regla de guardar
llamadas.

Ejemplo de código 6.26 LLAMADA A FUNCIÓN NONLEAF En una inspección cuidadosa, uno
podría notar que f2 no modifica R1,
por lo que f1 no necesitaba
Código de alto nivel Código de ensamblaje ARM
guardarlo y restaurarlo.
int f1 (int a, int b) { ; R0 = una, R1 = b, R4 = yo, R5 = x F1
Sin embargo, un compilador no siempre
int i, x;
PULSE {R4, R5, LR} ; guardar los registros conservados utilizados por puede determinar fácilmente qué
x = (a + b) * (a - B); para (i = AÑADIR R5, R0, R1 f1; x = (a + b) registros no conservados se pueden
0; i <a; i ++) SUB R12, R0, R1 ; temp = (a - B)
x = x + f2 (b + i); return alterar durante una llamada a una función.
MUL R5, R5, R12 ; x = x * temp = (a + b) * (a - B) ; i = 0
x; MOV R4, # 0 Por tanto, un compilador simple siempre
} POR
hará que la persona que llama guarde y
CMP R4, R0 ; yo <a?
BGE REGRESO ; no: bucle de salida restaure cualquier
PRESIONE {R0, R1} ; guardar registros no conservados; el registros no conservados que
AGREGAR R0, R1, R4 argumento es b + i
necesita después de la llamada.
F2
licenciado en Derecho ; llamar a f2 (b + i)
AGREGAR R5, R5, R0 ; x = x + f2 (b + i)
MÚSICA POP {R0, R1} ; restaurar registros no conservados; yo ++
AGREGAR R4, R4, n.º 1
B POR ; continuar para bucle
REGRESO
MOV R0, R5 ; el valor de retorno es x
MÚSICA POP {R4, R5, LR} ; restaurar registros conservados; regreso
MOV PC, LR de f1

; R0 = p, R4 = r F2 Un compilador optimizador podría

int f2 (int p) { observar que f2 es un procedimiento hoja


int r; PRESIONE {R4} ; guardar registros preservados usados por f2; r = y podría asignar
AGREGAR R4, R0, 5 p+5
r = p + 5; r a un registro no preservado,
AGREGAR R0, R4, R0 ; el valor de retorno es r + p
return r + p;
MÚSICA POP {R4} ; restaurar registros conservados; volver evitando la necesidad de guardar y
}
MOV PC, LR de f2 restaurar R4.
326 CAPITULO SEIS Arquitectura

Dirección Datos Dirección Datos Dirección Datos

BEF7FF0C ? SP BEF7FF0C ? BEF7FF0C ?


BEF7FF08 BEF7FF08 LR BEF7FF08 LR

marco de pila de F1
R5 R5

marco de pila de F1
BEF7FF04 BEF7FF04 BEF7FF04

BEF7FF00 BEF7FF00 R4 BEF7FF00 R4

BEF7FEFC BEF7FEFC R1 BEF7FEFC R1

BEF7FEF8 BEF7FEF8 R0 SP BEF7FEF8 R0

pila de f2
marco
BEF7FEF4 BEF7FEF4 BEF7FEF4 R4 SP

(a) (B) (C)

Figura 6.13 La pila: (a) antes de las llamadas a funciones, (b) durante f1, y (c) durante f2

Figura 6.13 muestra la pila durante la ejecución de f1. El puntero de pila


comienza originalmente en 0xBEF7FF0C.

Llamadas a funciones recursivas

A función recursiva es una función sin hojas que se llama a sí misma. Las funciones recursivas
se comportan tanto como llamador como como destinatario de la llamada y deben guardar
tanto los registros conservados como los no conservados. Por ejemplo, la función factorial se
puede escribir como una función recursiva. Recordar que factorial (n) = n × ( norte - 1)
× ( norte - 2) × ⋯ × 2 × 1. La función factorial se puede reescribir de forma recursiva como factorial
(n) = n × factorial (n - 1), como se muestra en el ejemplo de código 6.27. El factorial de 1 es
simplemente 1. Para referirnos cómodamente a las direcciones de los programas, mostramos
el programa que comienza en la dirección 0x8500.
De acuerdo con la regla de guardar llamadas, factorial es una función sin
hojas y debe guardar LR. De acuerdo con la regla de guardar llamadas, factorial necesitará
norte después de llamarse a sí mismo, por lo que debe guardar R0. Por lo
tanto, empuja ambos registros a la pila al principio. Luego comprueba si norte ≤
1. Si es así, pone el valor de retorno de 1 en R0, restaura el puntero de la pila y
regresa a la persona que llama. No es necesario recargar LR y R0 en este caso,
porque nunca se modificaron. Si n> 1, la función llama de forma recursiva factorial
(n - 1). Luego restaura el valor de norte y el registro de enlace (LR) de la pila,
realiza la multiplicación y devuelve este resultado. Tenga en cuenta que la
función restaura inteligentemente norte en R1, para no sobrescribir el valor
devuelto. La instrucción de multiplicar ( MUL R0, R1, R0) multiplica n ( R1) y el valor
devuelto (R0) y pone el resultado en R0.
6.3 Programación 327

Ejemplo de código 6.27 factorial LLAMADA A FUNCIÓN RECURSIVA

Código de alto nivel Código de ensamblaje ARM

int factorial (int n) { 0x8500 FACTORIAL PRESIONE {R0, LR} ; presione ny LR en la pila; R0 <=
si (n <= 1) 0x8504 CMP R0, n.º 1 1?
return 1; 0x8508 BGT DEMÁS ; no: rama a otra
0x850C MOV R0, n.º 1 ; de lo contrario, devuelve 1
0x8510 AGREGAR SP, SP, nº 8; restaurar SP MOV
0x8514 PC, LR ; regreso
demás 0x8518 DEMÁS SUB R0, R0, nº 1; n = n - 1 BL
retorno (n * factorial (n - 1)); 0x851C FACTORIAL; llamada recursiva POP
} 0x8520 {R1, LR} ; pop n (en R1) y LR
0x8524 MUL R0, R1, R0; R0 = n * factorial (n - 1)
0x8528 MOV PC, LR ; regreso

Figura 6.14 muestra la pila al ejecutar factorial (3). A modo de ilustración,


mostramos SP apuntando inicialmente a 0xBEFF0FF0, como se muestra en Figura
6.14 (a) . La función crea un marco de pila de dos palabras para contener n ( R0) y
LR. En la primera invocación, factorial guarda R0 (sosteniendo n = 3) en Para mayor claridad, siempre guardaremos

0xBEFF0FE8 y LR en 0xBEFF0FEC, como se muestra en Figura 6.14 (b) . La registros al comienzo de una llamada a

procedimiento. Un compilador optimizador


función luego cambia norte a 2 y llama de forma recursiva factorial (2), haciendo
puede observar que no es necesario guardar
que LR mantenga 0x8520. En la segunda invocación, guarda R0 (manteniendo
R0 y LR cuando norte ≤ 1 y, por lo tanto, empuja
n = 2) en 0xBEFF0FE0 y LR en 0xBEFF0FE4. Esta vez, sabemos que LR contiene
los registros solo en el
0x8520. La función luego cambia norte a 1 y llama de forma recursiva DEMÁS parte de la función.
factorial (1). En la tercera invocación, guarda R0 (manteniendo n = 1) en

Dirección Datos Dirección Datos Dirección Datos

BEFF0FF0 SP BEFF0FF0 BEFF0FF0 SP R0 = 6

BEFF0FEC BEFF0FEC LR BEFF0FEC LR


n=3
BEFF0FE8 BEFF0FE8 R0 (3) SP BEFF0FE8 R0 (3) SP R0 = 3 x 2
BEFF0FE4 BEFF0FE4 LR (0x8520) BEFF0FE4 LR (0x8520)
n=2
BEFF0FE0 BEFF0FE0 R0 (2) SP BEFF0FE0 R0 (2) SP
R0 = 2 x 1
BEFF0FDC BEFF0FDC LR (0x8520) BEFF0FDC LR (0x8520)

BEFF0FD8 BEFF0FD8 R0 (1) SP BEFF0FD8 R0 (1) SP R0 = 1

(a) (B) (C)

Figura 6.14 Apilar: (a) antes, (b) durante y (c) después de la llamada a la función factorial con n = 3
328 CAPITULO SEIS Arquitectura

0xBEFF0FD8 y LR en 0xBEFF0FDC. Esta vez, LR nuevamente contiene 0x8520. La


tercera invocación de factorial devuelve el valor 1 en R0 y desasigna el marco de
la pila antes de volver a la segunda invocación. La segunda invocación restaura n
( en R1) a 2, restaura LR a 0x8520 (ya tenía este valor), desasigna el marco de la
pila y devuelve R0 = 2 × 1 = 2 a la primera invocación. La primera invocación
restaura
n ( en R1) a 3, restaura LR a la dirección de retorno de la persona que llama, desasigna el
marco de la pila y devuelve R0 = 3 × 2 = 6. Figura 6.14 (c) muestra la pila como retornan
las funciones llamadas recursivamente. Cuándo factorial vuelve a la persona que llama, el
puntero de la pila está en su posición original (0xBEFF0FF0), ninguno de los contenidos
de la pila encima del puntero ha cambiado y todos los registros conservados mantienen
sus valores originales. R0 contiene el valor de retorno, 6.

Argumentos adicionales y variables locales *


Las funciones pueden tener más de cuatro argumentos de entrada y pueden tener demasiadas variables
locales para mantenerlas en registros preservados. La pila se utiliza para almacenar esta información. Por
convención ARM, si una función tiene más de cuatro argumentos, los primeros cuatro se pasan en los registros
de argumentos como de costumbre. Los argumentos adicionales se pasan a la pila, justo encima de SP. La
persona que llama debe expandir su pila para dejar espacio para los argumentos adicionales.

Figura 6.15 (a) muestra a la persona que llama ' s pila para llamar a una función con más
de cuatro argumentos.
Una función también puede declarar matrices o variables locales. Las variables
locales se declaran dentro de una función y solo se puede acceder a ellas dentro de esa
función. Las variables locales se almacenan en R4 - R11; si hay demasiadas variables
locales, también se pueden almacenar en la función ' s marco de pila. En particular, las
matrices locales se almacenan en la pila.
Figura 6.15 (b) muestra la organización de un destinatario ' s marco de pila. El marco de pila
contiene los registros temporales y el registro de enlace (si es necesario guardarlos debido a
una llamada de función posterior) y cualquiera de los registros guardados.

Argumentos adicionales Argumentos adicionales


SP
R0 - R12, LR
Figura 6.15 Uso de la pila: (a) antes y
Marco de pila

(si es necesario)
(b) después de la llamada
Variables locales o
matrices
SP

(a) (B)
6.4 Lenguaje de máquina 329

registra que la función se modificará. También contiene matrices locales y cualquier exceso de
variables locales. Si el destinatario de la llamada tiene más de cuatro argumentos, los
encuentra en el llamador ' s marco de pila. El acceso a argumentos de entrada adicionales es la
única excepción en la que una función puede acceder a datos de pila que no están en su propio
marco de pila.

6.4 LENGUAJE DE MÁQUINA

El lenguaje ensamblador es conveniente para que los humanos lo lean. Sin


embargo, los circuitos digitales entienden solo 1 ' sy 0 ' s. Por lo tanto, un programa
escrito en lenguaje ensamblador se traduce de mnemotécnicos a una
representación utilizando solo 1 ' sy 0 ' s llamado Lenguaje de máquina. Esta sección
describe el lenguaje de máquina ARM y el tedioso proceso de conversión entre
lenguaje ensamblador y lenguaje de máquina.
ARM usa instrucciones de 32 bits. Una vez más, la regularidad apoya la simplicidad,
y la opción más habitual es codificar todas las instrucciones como palabras que se
pueden almacenar en la memoria. Aunque algunas instrucciones pueden no requerir los
32 bits de codificación, las instrucciones de longitud variable agregarían complejidad. La
simplicidad también fomentaría un formato de instrucción único, pero eso es demasiado
restrictivo. Sin embargo, esta cuestión nos permite introducir el último principio de
diseño:

Principio de diseño 4: Un buen diseño exige buenos compromisos.

ARM se compromete a definir tres formatos de instrucción principales: Procesamiento


de datos, memoria, y Rama. Esta pequeña cantidad de formatos permite cierta
regularidad entre las instrucciones y, por lo tanto, un hardware decodificador más
simple, al mismo tiempo que se adapta a diferentes necesidades de instrucción. Las
instrucciones de procesamiento de datos tienen un primer registro de fuente, una
segunda fuente que es inmediata o un registro, posiblemente desplazado, y un registro
de destino. El formato de procesamiento de datos tiene varias variaciones para estas
segundas fuentes. Las instrucciones de memoria tienen tres operandos: un registro base,
un desplazamiento que es un registro inmediato u opcionalmente desplazado, y un
registro que es el destino en un registro. LDR y otra fuente en un STR.
Las instrucciones de bifurcación toman un desplazamiento de bifurcación inmediato de
24 bits. Esta sección analiza estos formatos de instrucción ARM y muestra cómo se
codifican en binario. El Apéndice B proporciona una referencia rápida para todas las
instrucciones de ARMv4.

6. 4. 1 Instrucciones de procesamiento de datos

El formato de instrucción de procesamiento de datos es el más común. El primer


operando fuente es un registro. El segundo operando fuente puede ser un registro
inmediato u opcionalmente desplazado. Un tercer registro es el destino. Figura 6.16 muestra
el formato de instrucción de procesamiento de datos. La instrucción de 32 bits tiene
seis campos: cond, op, funct, Rn, Rd, y Src2.
330 CAPITULO SEIS Arquitectura

Procesamiento de datos

Figura 6.16 Procesamiento de datos 31:28 27:26 25:20 19:16 15:12 11: 0

formato de instrucción cond op funct Rn Rd Src2


4 bits 2 bits 6 bits 4 bits 4 bits 12 bits

La operación que realiza la instrucción está codificada en los campos resaltados en


azul: op también llamado código de operación o código de operación) y funct
o código de función; la cond El campo codifica la ejecución condicional basada en
banderas descritas en Sección 6.3.2 . Recordar que cond = 1110 2 por uncondi-
instrucciones técnicas. op es 00 2 para instrucciones de procesamiento de datos.
Los operandos están codificados en los tres campos: Rn, Rd, y Src2. Rn es el
primer registro de fuente y Src2 es la segunda fuente; Rd es el registro de destino.
Figura 6.17 muestra el formato de la funct campo y las tres variaciones de Src2 para
instrucciones de procesamiento de datos. funct tiene tres subcampos:
Yo, cmd, y S. El I- bit es 1 cuando Src2 es un inmediato. El S- El bit es 1 cuando la
instrucción establece los indicadores de condición. Por ejemplo, SUBS R1, R9, # 11 tiene
S = 1. cmd indica la instrucción específica de procesamiento de datos,
como se indica en la Tabla B.1 del Apéndice B. Por ejemplo, cmd es 4 (0100 2) por
AGREGAR y 2 (0010 2) por SUB.
Tres variaciones de Src2 La codificación permite que el segundo operando fuente
Rd es la abreviatura de " registrar
destino. " Rn y Rm
ser (1) un inmediato, (2) un registro ( Rm) opcionalmente desplazado por una
Indique de forma poco intuitiva las
constante ( shamt5), o (3) un registro ( Rm) desplazado por otro registro Rs). Para las
fuentes de registro primero y segundo. dos últimas codificaciones de Src2, sh codifica el tipo de turno a realizar, como se
mostrará en Cuadro 6.8 .
Las instrucciones de procesamiento de datos tienen una representación inmediata inusual
que involucra un inmediato sin firmar de 8 bits, imm8, y una rotación de 4 bits,
putrefacción. imm8 se gira a la derecha en 2 × putrefacción para crear una constante de 32 bits.
Cuadro 6.7 da ejemplos de rotaciones y constantes de 32 bits resultantes para
el 0xFF inmediato de 8 bits. Esta representación es valiosa porque

Inmediato
11: 8 7: 0

putrefacción imm8

Procesamiento de datos
Yo = 1
31:28 27:26 25 24:21 20 19:16 15:12 11: 0 11: 7 6: 5 4 3: 0

cond op cmd S Rn Rd Src2 sh 0


00 I
Registrarse shamt5 Rm

funct Yo = 0
11: 8 7 6: 5 4 3: 0

Rs 0 sh 1 Rm

Registro cambiado
Registrarse

Figura 6.17 Formato de instrucción de procesamiento de datos que muestra funct campo y Src2 variaciones
6.4 Lenguaje de máquina 331

Cuadro 6.7 Rotaciones inmediatas y constante de 32 bits resultante para imm8 = 0xFF
Si un inmediato tiene múltiples
putrefacción Constante de 32 bits codificaciones posibles, el
representación con el
0000 0000 0000 0000 0000 0000 0000 1111 1111 valor de rotación más pequeño putrefacción
se utiliza. Por ejemplo, el # 12 se
0001 1100 0000 0000 0000 0000 0000 0011 1111
representaría como ( pudrición, imm8) =

0010 1111 0000 0000 0000 0000 0000 0000 1111 (0000, 00001100), no
(0001, 00110000).
... ...

1111 0000 0000 0000 0000 0000 0011 1111 1100

permite empaquetar muchas constantes útiles, incluidos pequeños múltiplos de


cualquier potencia de dos, en un pequeño número de bits. Sección 6.6.1 describe
cómo generar constantes arbitrarias de 32 bits.
Figura 6.18 muestra el código de la máquina para AGREGAR y SUB Cuándo Src2 es
un registro. La forma más fácil de traducir de ensamblador a código de máquina es
escribir los valores de cada campo y luego convertir estos valores a binarios.
Agrupe los bits en bloques de cuatro para convertirlos a hexadecimal y hacer que la
representación en lenguaje de máquina sea más compacta. Tenga en cuenta que el
destino es el primer registro en una instrucción en lenguaje ensamblador, pero es
el segundo campo de registro ( Rd) en la instrucción del lenguaje de máquina. Rn y Rm
son el primer y segundo operandos de origen, respectivamente. Por ejemplo, la
instrucción de montaje AÑADIR R5, R6, R7 tiene Rn = 6,
Rd = 5, y Rm = 7.
Figura 6.19 muestra el código de la máquina para AGREGAR y SUB con
operandos de registro inmediato y dos. De nuevo, el destino es el primero

Código de montaje Valores de campo Codigo de maquina

31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0 31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0
AÑADIR R5, R6, R7
1110 2 00 2 0 0100 2 0 6 5 0 0 0 7 1110 00 0 0100 0 0110 00 0 0101 00000 00 0 0111
(0xE0865007)
SUB R8, R9, R10 1110 2 00 2 0 0010 2 0 9 8 0 0 0 10 1110 0010 0 1001 1000 00000 00 0 1010
(0xE049800A)
cond op yo cmd S Rn Rd shamt5 sh Rm cond op yo cmd S Rn Rd shamt5 sh Rm

Figura 6.18 Instrucciones de procesamiento de datos con tres operandos de registro

Código de montaje Valores de campo Codigo de maquina

31:28 27:26 25 24:21 20 19:16 15:12 11: 8 7: 0 31:28 27:26 25 24:21 20 19:16 15:12 11: 8 7: 0
AÑADIR R0, R1, # 42
(0xE281002A) 1110 2 00 2 1 0100 2 0 1 0 0 42 1110 00 1 0100 0 0001 0000 0000 00101010
SUB R2, R3, # 0xFF0 1110 2 00 2 1 0010 2 0 3 2 14 255 1110 00 1 0010 0 0011 op I 0010 1110 11111111
(0xE2432EFF)
cond op yo cmd S Rn Rd putrefacción imm8 cond cmd S Rn Rd putrefacción imm8

Figura 6.19 Instrucciones de procesamiento de datos con operandos inmediatos y de dos registros
332 CAPITULO SEIS Arquitectura

registrarse en una instrucción en lenguaje ensamblador, pero es el segundo campo


de registro ( Rd) en la instrucción del lenguaje de máquina. Lo inmediato de la
AGREGAR La instrucción (42) se puede codificar en 8 bits, por lo que no se necesita
rotación ( imm8 = 42, rot = 0). Sin embargo, lo inmediato de SUB R2, R3, 0xFF0 no se puede
codificar directamente utilizando los 8 bits de imm8. En lugar de, imm8 es 255 (0xFF), y se
gira a la derecha 28 bits ( rot = 14). Esto es más fácil de interpretar recordando que la
rotación a la derecha en 28 bits es equivalente a una rotación a la izquierda en 32 - 28 = 4
bits.
Los turnos también son instrucciones de procesamiento de datos. Recordar de Sección 6.3.1
que la cantidad por la que cambiar se puede codificar utilizando un registro inmediato o
un registro de 5 bits.
Figura 6.20 muestra el código de la máquina para el desplazamiento lógico a la izquierda ( LSL)
y rotar a la derecha ( ROR) con montos de turno inmediatos. El cmd campo
es 13 (1101 2) para todas las instrucciones de turno, y el campo de turno ( sh) codifica el
tipo de turno a realizar, como se indica en Cuadro 6.8 . Rm ( es decir, R5) contiene el
Valor de 32 bits que se va a cambiar, y shamt5 da el número de bits a cambiar.
El resultado desplazado se coloca en Rd. Rn no se usa y debe ser 0.
Figura 6.21 muestra el código de la máquina para LSR y ASR con la cantidad de
cambio codificada en los 8 bits menos significativos de Rs ( R6 y R12). Como

Cuadro 6.8 sh codificaciones de campo

Instrucción sh Operación

LSL 00 2 Desplazamiento lógico a la izquierda

LSR 01 2 Desplazamiento lógico a la derecha

ASR 10 2 Desplazamiento aritmético a la derecha

ROR 11 2 Gira a la derecha

Código de montaje Valores de campo Codigo de maquina


31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0 31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0
LSL R0, R9, n.º 7
(0xE1A00389)
1110 2 00 2 0 1101 2 0 0 0 7 00 2 0 9 1110 00 0 1101 0 0000 00 0 0000 00111 00 0 1001
ROR R3, R5, n.º 21 1110 2 00 2 0 1101 2 0 0 3 21 11 2 0 5 1110 1101 0 0000 0011 10101 11 0 0101
(0xE1A03AE5)
cond op yo cmd S Rn Rd shamt5 sh Rm cond op yo cmd S Rn Rd shamt5 sh Rm

Figura 6.20 Instrucciones de turno con cantidades de turno inmediatas

Código de montaje Valores de campo Codigo de maquina

31:28 27:26 25 24:21 20 19:16 15:12 11: 8 7 6: 5 4 3: 0 31:28 27:26 25 24:21 20 19:16 15:12 11: 8 7 6: 5 4 3: 0
LSR R4, R8, R6
1110 2 00 2 0 1101 2 0 0 4 6 0 01 2 1 8 1110 00 0 1101 0 0000 0100 0110 0 01 1 1000
(0xE1A04638)
ASR R5, R1, R12 1110 2 00 2 0 1101 2 0 0 5 12 0 10 2 1 1 1110 00 0 1101 0 0000 0101 1100 0 10 1 0001
(0xE1A05C51)
cond op yo cmd S Rn Rd Rs sh Rm cond op yo cmd S Rn Rd Rs sh Rm

Figura 6.21 Instrucciones de turno con cantidades de turno de registro


6.4 Lenguaje de máquina 333

Cuadro 6.9 Bits de control de tipo offset para instrucciones de memoria

Sentido
Poco I U

0 Desplazamiento inmediato en Src2 Restar el desplazamiento de la base

1 Registro de compensación en Src2 Agregar desplazamiento a la base

antes de, cmd es 13 (1101 2), sh codifica el tipo de turno, Rm contiene el valor que se
Cuadro 6.10 Bits de control de modo de
va a cambiar, y el resultado cambiado se coloca en Rd. Esta instrucción usa índice para instrucciones de memoria
la registro de registro desplazado modo de direccionamiento, donde un registro ( Rm) se
desplaza por la cantidad mantenida en un segundo registro ( Rs). Debido a que los 8 bits PAG Modo de índice W
menos significativos de Rs son usados, Rm se puede cambiar hasta 255 posiciones. Por 0 0 Post-índice
ejemplo, si Rs tiene el valor 0xF001001C, la cantidad de cambio es 0x1C (28). Un
desplazamiento lógico de más de 31 bits empuja todos los bits al final y produce todos 0 1 No soportado
los ceros. La rotación es cíclica, por lo que una rotación de 50 bits equivale a una rotación
1 0 Compensar
de 18 bits.
1 1 Pre-índice
6. 4. 2 Instrucciones de memoria

Las instrucciones de memoria utilizan un formato similar al de las instrucciones de


procesamiento de datos, con los mismos seis campos generales: cond, op, funct, Rn, Rd,
y Src2, como se muestra en Figura 6.22 . Sin embargo, las instrucciones de memoria usan Cuadro 6.11 Bits de control del tipo de operación

un funct codificación de campo, tienen dos variaciones de Src2, y usa un de memoria para instrucciones de memoria

op de 01 2. Rn es el registro base, Src2 mantiene el desplazamiento, y Rd es el registro de


L B Instrucción
destino en una carga o el registro de origen en una tienda. El desplazamiento es
ya sea un inmediato sin firmar de 12 bits ( imm12) o un registro Rm) que 0 0 STR
opcionalmente se desplaza por una constante ( sh a mt5). funct se compone de
0 1 STRB
seis bits de control: Yo, P, U, B, W, y L. El I ( inmediato) y U ( add) bits determinan
si el desplazamiento es inmediato o de registro y si se debe sumar o restar, de 1 0 LDR
acuerdo con Cuadro 6.9 . El PAG ( pre-índice) y W
1 1 LDRB
Los bits (escritura diferida) especifican el modo de índice de acuerdo con Cuadro 6.10 . El L
(carga) y B ( byte) bits especifican el tipo de operación de memoria de acuerdo con Cuadro
6.11 .

Memoria Inmediato 11: 0


Yo = 0

31:28 27:26 25:20 19:16 15:12 11: 0


imm12
op
cond 01 IP U B W L Rn Rd Src2 11: 7 6: 5 4 3: 0

funct shamt5 sh 1 Rm
Yo = 1
Registrarse

Figura 6.22 Formato de instrucción de memoria para LDR, STR, LDRB, y STRB
334 CAPITULO SEIS Arquitectura

Ejemplo 6.3 TRADUCCIÓN DE LAS INSTRUCCIONES DE LA MEMORIA AL LENGUAJE DE


MÁQUINA

Traduzca la siguiente declaración en lenguaje ensamblador a lenguaje de máquina.

STR R11, [R5], n.º -26

Solución: STR es una instrucción de memoria, por lo que tiene una op de 01 2. De acuerdo a
Note lo contrario a la intuición
Cuadro 6.11 , L = 0 y B = 0 para STR. La instrucción utiliza post-indexación,
codificación de post-indexación
modo.
así que de acuerdo con Cuadro 6.10 , P = 0 y W = 0. El desplazamiento inmediato se resta de la
base, por lo que Yo = 0 y U = 0. Figura 6.23 muestra cada campo y el código de la máquina. Por
lo tanto, la instrucción en lenguaje de máquina es 0xE405B01A.

Código de montaje Valores de campo Codigo de maquina

31:28 27:26 25:20 19:16 15:12 11: 0 31:28 27:26 25:20 19:16 15:12 11: 0

STR R11, [R5], n.º -26 1110 2 01 2 0000000 2 5 11 26 1110 01 000000 0101 1011 0000 0001 1010
cond op IPUBWL Rn Rd imm12 mi 4 0 5 B 0 1 A

Figura 6.23 Código de máquina para la instrucción de memoria del ejemplo 6.3

6. 4. 3 Instrucciones de sucursal

Las instrucciones de bifurcación utilizan un solo operando inmediato firmado de 24 bits, imm24,
como se muestra en Figura 6.24 . Al igual que con las instrucciones de procesamiento de datos y
memoria, las instrucciones de bifurcación comienzan con un campo de condición de 4 bits y un
op, que es 10 2. El funct El campo tiene solo 2 bits. La parte superior de funct es
siempre 1 para las sucursales. La parte inferior L, indica el tipo de rama
operación: 1 para licenciado en Derecho y 0 para B. Los dos restantes de 24 bits ' s complemento
imm24 El campo se utiliza para especificar una dirección de instrucción relativa a PC + 8.
El ejemplo de código 6.28 muestra el uso de la rama si es menor que ( BLT)
instrucción y Figura 6.25 muestra el código de máquina para esa instrucción. El dirección
de destino de sucursal (BTA) es la dirección de la siguiente instrucción a ejecutar si
se toma la rama. El BLT instrucción en Figura 6.25 tiene un BTA de 0x80B4, la
dirección de instrucción del ALLÍ etiqueta.
El campo inmediato de 24 bits proporciona el número de instrucciones
entre el BTA y el PC + 8 (dos instrucciones más allá de la rama). En este caso, el
valor del campo inmediato ( imm24) de BLT es 3 porque el BTA (0x80B4) es tres
instrucciones más allá de PC + 8 (0x80A8).

Rama
31:28 27:26 25:24 23: 0
op
cond 10 1 litro imm24

funct

Figura 6.24 Formato de instrucción de rama


6.4 Lenguaje de máquina 335

Ejemplo de código 6.28 CÁLCULO DE LA DIRECCIÓN DE OBJETIVO DE LA RAMA

Código de ensamblaje ARM

0x80A0 BLT ALLÍ


0x80A4 AÑADIR R0, R1, R2
0x80A8 SUB R0, R0, R9
0x80AC AÑADIR SP, SP, # 8
0x80B0 MOV PC, LR
0x80B4 ALLÍ SUB R0, R0, # 1
0x80B8 AÑADIR R3, R3, # 0x5

Código de montaje Valores de campo Codigo de maquina

31:28 27:26 25:24 23: 0 31:28 27:26 25:24 23: 0

BLT ALLÍ 1011 2 10 2 10 2 3 1011 10 10 0000 0000 0000 0000 0000 0011
(0xBA000003)
cond opfunct imm24 cond op funct imm24

Figura 6.25 Código de máquina para la rama si es menor que ( BLT)

El procesador calcula el BTA a partir de la instrucción extendiendo el signo


inmediato de 24 bits, desplazándolo a la izquierda en 2 (para convertir palabras en bytes)
y agregándolo a PC + 8.

Ejemplo 6.4 CÁLCULO DEL CAMPO INMEDIATO PARA DIRECCIÓN RELATIVA AL PC

Calcule el campo inmediato y muestre el código de máquina para la instrucción de bifurcación


en el siguiente programa de ensamblaje.

PRUEBA 0x8040 LDRB R5, [R0, R3]


0x8044 STRB R5, [R1, R3]
0x8048 AGREGAR R3, R3, n.º 1
0x8044 MOV PC, LR
0x8050 PRUEBA
licenciado en Derecho

0x8054 LDR R3, [R1], n.º 4


0x8058 SUB R4, R3, n.º 9

Solución: Figura 6.26 muestra el código de máquina para la rama y la instrucción de enlace ( LICENCIADO
EN DERECHO). Su dirección de destino de rama (0x8040) está seis instrucciones detrás de PC + 8 (0x8058),

por lo que el campo inmediato es -6.

Montaje
Valores de campo Codigo de maquina
Código
31:28 27:26 25:24 23: 0 31:28 27:26 25:24 23: 0
PRUEBA BL 1110
2 10 2 11 2 -6 1110 10 11 1111 1111 1111 1111 1111 1010
(0xEBFFFFFA)
cond op funct imm24 cond op funct imm24

Figura 6.26 licenciado en Derecho codigo de maquina


336 CAPITULO SEIS Arquitectura

6. 4. 4 Modos de direccionamiento
ARM es inusual entre las arquitecturas
RISC porque permite que el segundo Esta sección resume los modos usados para direccionar operandos de instrucción. ARM utiliza
operando de origen se cambie en los
cuatro modos principales: registro, inmediato, base y direccionamiento relativo a PC. La
modos de direccionamiento de registro y
mayoría de las otras arquitecturas proporcionan modos de direccionamiento similares, por lo
base. Esta
que comprender estos modos le ayuda a aprender fácilmente otros lenguajes ensambladores.
requiere una palanca de cambios en
El registro y el direccionamiento base tienen varios submodos que se describen a continuación.
serie con la ALU en la implementación
Los primeros tres modos (registro, inmediato y direccionamiento base) definen modos de
del hardware, pero
reduce significativamente el código
lectura y escritura de operandos. El último modo (direccionamiento relativo a PC) define el
longitud en programas comunes, modo de escribir el contador de programa (PC). Cuadro 6.12
especialmente accesos a matrices. Por resume y da ejemplos de cada modo de direccionamiento.
ejemplo, en una matriz de elementos de Las instrucciones de procesamiento de datos usan registro o direccionamiento
datos de 32 bits, el índice de la matriz debe inmediato, en el que el primer operando fuente es un registro y el segundo es un
desplazarse a la izquierda en 2 para registro o inmediato, respectivamente. ARM permite que el segundo registro sea
calcular el desplazamiento de bytes en la
opcionalmente desplazado por una cantidad especificada en un registro inmediato o en
matriz. Se permite cualquier tipo de turno,
un tercer registro. Las instrucciones de memoria usan direccionamiento base, en el que
pero los turnos a la izquierda para la
la dirección base proviene de un registro y el desplazamiento proviene de un inmediato,
multiplicación son más
un registro o un registro desplazado por un inmediato. Las sucursales utilizan
común.
direccionamiento relativo a PC en el que la dirección de destino de la sucursal se calcula
agregando un desplazamiento a PC + 8.

6. 4. 5 Interpretación del código de lenguaje de máquina

Para interpretar el lenguaje de máquina, es necesario descifrar los campos de cada palabra de
instrucción de 32 bits. Diferentes instrucciones usan diferentes formatos, pero todos

Cuadro 6.12 Modos de direccionamiento de operandos ARM

Modo de direccionamiento de operando Ejemplo Descripción

Registrarse

Solo registro AÑADIR R3, R2, R1 R3 ← R2 + R1

Registro de cambio inmediato SUB R4, R5, R9, LSR # 2 ORR R0, R4 ← R5 - ( R9 >> 2) R0 ← R10 |

Registro de cambio de registro R10, R2, ROR R7 SUB R3, R2, # (R2 ROR R7) R3 ← R2 - 25

Inmediato 25

Base

Compensación inmediata STR R6, [R11, # 77] LDR mem [R11 + 77] ← R6

Desplazamiento de registro R12, [R1, - R5] R12 ← mem [R1 - R5] R8 ← mem

Desplazamiento de registro con desplazamiento inmediato LDR R8, [R9, R2, LSL # 2] B [R9 + (R2 << 2)]

Relativo a PC ETIQUETA1 Bifurcar a LABEL1


6.4 Lenguaje de máquina 337

Los formatos comienzan con un campo de condición de 4 bits y un campo de condición de 2 bits. op. El mejor lugar para

comenzar es mirar el op. Si es 00 2, entonces la instrucción es una instrucción de procesamiento de datos; si es


01 2, entonces la instrucción es una instrucción de memoria; si
son las 10 2, entonces es una instrucción de bifurcación. En base a eso, el resto de los campos se
pueden interpretar.

Ejemplo 6.5 TRADUCCIÓN DEL LENGUAJE DE LA MÁQUINA AL LENGUAJE DE


MONTAJE

Traduzca el siguiente código de lenguaje de máquina a lenguaje ensamblador.

0xE0475001
0xE5949010

Solución: Primero, representamos cada instrucción en binario y miramos los bits 27:26 para
encuentra el op para cada instrucción, como se muestra en Figura 6.27 . El op los campos son 00 2
y 01 2, indicando un procesamiento de datos y una instrucción de memoria, respectivamente. A
continuación, miramos el funct campo de cada instrucción.

El cmd El campo de la instrucción de procesamiento de datos es 2 (0010 2) y el I- bit (bit 25) es 0,


lo que indica que es un SUB instrucción con un registro Src2. Rd es 5, Rn es 7, y Rm es 1.

El funct el campo para la instrucción de memoria es 011001 2. B = 0 y L = 1, entonces este es un LDR instrucción.
P = 1 y W = 0, que indica direccionamiento offset. Yo = 0, entonces el
La compensación es inmediata. U = 1, por lo que se agrega el desplazamiento. Por lo tanto, es una instrucción
de registro de carga con un desplazamiento inmediato que se agrega al registro base. Rd es 9,
Rn es 4, y imm12 es 16. Figura 6.27 muestra el código ensamblador equivalente a las dos
instrucciones de la máquina.

Codigo de maquina Valores de campo Código de montaje


cond op I cmd S Rn Rd shamt5 sh Rm
31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0 31:28 27:26 25 24:21 20 19:16 15:12 11: 7 6: 5 4 3: 0

1110 00 0 0010 0 0111 0101 00000 00 0 0001 1110 2 00 20 2 0 7 5 0 0 0 1 SUB R5, R7, R1

mi 0 4 7 5 0 0 1 cond op I cmd S Rn Rd shamt5 sh Rm

cond op IPUBWL Rn Rd imm12


31:28 27:26 25:20 19:16 15:12 11: 0 31:28 27:26 25:20 19:16 15:12 11: 0

1110 01 011001 0100 1001 0000 0001 0000 1110 2 01 2 25 4 9 dieciséis LDR R9, [R4, # 16]

mi 5 9 4 9 0 1 0 cond op IPUBWL Rn Rd imm12

Figura 6.27 Traducción de código máquina a código ensamblador

6. 4. 6 El poder del programa almacenado

Un programa escrito en lenguaje de máquina es una serie de números de 32 bits que representan las
instrucciones. Como otros números binarios, estas instrucciones se pueden almacenar en la memoria. Esto se
llama programa almacenado concepto, y es una razón clave por la que las computadoras son tan poderosas.
Ejecutando una diferente
338 CAPITULO SEIS Arquitectura

Programa almacenado

Dirección Instrucciones

Código de ensamblaje Codigo de maquina

MOV R1, n.º 100 0xE3A01064


0000800C 25813024
MOV R2, n. ° 69 0xE3A02045
00008008 E1510002
CMP R1, R2 0xE1510002
00008004 E3A02045
STRHS R3, [R1, # 0x24] 0x25813024
00008000 E3A01064 ordenador personal

Memoria principal

Figura 6.28 Programa almacenado

el programa no requiere una gran cantidad de tiempo y esfuerzo para reconfigurar o volver a
cablear el hardware; solo requiere escribir el nuevo programa en la memoria. A diferencia del
hardware dedicado, el programa almacenado ofrece computación de propósito general. De
esta manera, una computadora puede ejecutar aplicaciones que van desde una calculadora
hasta un procesador de texto y un reproductor de video simplemente cambiando el programa
almacenado.
Se recuperan las instrucciones de un programa almacenado, o traído, de la memoria y
ejecutado por el procesador. Incluso los programas grandes y complejos son simplemente una
serie de lecturas de memoria y ejecuciones de instrucciones.
Figura 6.28 muestra cómo se almacenan las instrucciones de la máquina en la memoria. En los
programas ARM, las instrucciones normalmente se almacenan comenzando en direcciones bajas, en este caso
0x00008000. Recuerde que la memoria ARM se puede eliminar mediante bytes, por lo que las direcciones de
instrucción de 32 bits (4 bytes) avanzan 4 bytes, no 1.
Para ejecutar o ejecutar el programa almacenado, el procesador obtiene las
instrucciones de la memoria de forma secuencial. Las instrucciones obtenidas son
luego decodificadas y ejecutadas por el hardware digital. La dirección de la
instrucción actual se mantiene en un registro de 32 bits llamado contador de
programa (PC), que es el registro R15. Por razones históricas, una lectura en la PC
devuelve la dirección de la instrucción actual más 8.
Para ejecutar el código en Figura 6.28 , la PC se inicializa para direccionar
0x00008000. El procesador obtiene la instrucción en esa dirección de memoria
y ejecuta la instrucción, 0xE3A01064 ( MOV R1, # 100). Luego, el procesador
incrementa la PC en 4 a 0x00008004, busca y ejecuta esa instrucción y la repite.
Ada Lovelace, 1815 - 1852.
Un matemático británico que
El estado arquitectónico de un microprocesador mantiene el estado de un
escribió el primer programa de
programa. Para ARM, el estado de la arquitectura incluye el archivo de registro y los
computadora. Calculó los números
registros de estado. Si el sistema operativo (SO) guarda el estado de la arquitectura en
de Bernoulli usando
algún punto del programa, puede interrumpir el programa, hacer otra cosa y luego
Charles Babbage ' s analítico
Motor. Ella era la hija del poeta restaurar el estado de manera que el programa continúe correctamente, sin saber que
Lord Byron. alguna vez fue interrumpido. El estado arquitectónico también es de gran importancia
cuando construimos un microprocesador en el Capítulo 7.
6.5 Luces, cámara, acción: compilación, montaje y carga 339

6.5 LUCES, CÁMARA, ACCIÓN: COMPILACIÓN, MONTAJE, Código de alto nivel

Y CARGANDO *
Compilador
Hasta ahora, hemos mostrado cómo traducir fragmentos de código cortos de alto
nivel en código ensamblador y de máquina. Esta sección describe cómo compilar y
Código de ensamblaje
ensamblar un programa completo de alto nivel y cómo cargar el programa en la
memoria para su ejecución. Comenzamos presentando un ARM de ejemplo
Ensamblador
mapa de memoria, que define dónde se encuentran el código, los datos y la memoria de pila.
Figura 6.29 muestra los pasos necesarios para traducir un programa de un lenguaje Archivos de objeto
Archivo de objeto
de alto nivel al lenguaje de máquina y comenzar a ejecutar ese programa. Primero un compilador Archivos de biblioteca

traduce el código de alto nivel en código ensamblador. El


Enlazador
ensamblador traduce el código ensamblador a código de máquina y lo coloca en un archivo de
objeto. El enlazador combina el código de la máquina con el código de las bibliotecas y otros
archivos y determina las direcciones de sucursal adecuadas y las ubicaciones de las variables Ejecutable
para producir un programa ejecutable completo. En la práctica, la mayoría de los compiladores
realizan tres pasos: compilar, ensamblar y vincular. Finalmente, el Cargador

cargador carga el programa en la memoria y comienza la ejecución. El resto de


esta sección describe estos pasos para un programa simple. Memoria

Figura 6.29 Pasos para traducir e


6. 5. 1 El mapa de la memoria
iniciar un programa

Con direcciones de 32 bits, el espacio de direcciones ARM abarca 2 32 bytes (4


GB). Las direcciones de palabra son múltiplos de 4 y van de 0 a 0xFFFFFFFC.
Figura 6.30 muestra un mapa de memoria de ejemplo. La arquitectura ARM divide
el espacio de direcciones en cinco partes o segmentos: el segmento de texto,

Dirección Segmento
0xFFFFFFFC
Operando
Sistema y E / S
0xC0000000
0xBEFFFAE8 Apilar SP

Datos dinámicos

Montón

Datos globales

SB

Texto

0x00008000

Excepción
manipuladores
0x00000000 ordenador personal

Figura 6.30 Ejemplo de mapa de memoria ARM


340 CAPITULO SEIS Arquitectura

segmento de datos globales, segmento de datos dinámicos y segmentos para controladores de


Presentamos un mapa de memoria
excepciones, el sistema operativo (SO) y la entrada / salida (E / S). Las siguientes secciones
ARM de ejemplo aquí; sin embargo,
en ARM, el mapa de memoria es describen cada segmento.
algo flexible. Si bien la tabla de El segmento de texto
vectores de excepciones debe
El segmento de texto almacena el programa en lenguaje de máquina. ARM también
ubicarse en 0x0 y las E / S asignadas
llama a esto el solo lectura ( RO) segmento. Además del código, puede incluir literales
en memoria
(constantes) y datos de solo lectura.
ubicadas en las direcciones de
memoria alta, el usuario puede El segmento de datos globales
definir dónde se ubican el texto El segmento de datos global almacena variables globales a las que, a diferencia de las
(código y datos constantes), la pila
variables locales, todas las funciones de un programa pueden acceder a ellas. Las
y los datos globales.
variables globales se asignan en la memoria antes de que el programa comience a
Además, al menos históricamente, la
ejecutarse. ARM también llama a esto el leer escribir ( RW) segmento. Por lo general, se
mayoría de los sistemas ARM tienen
accede a las variables globales mediante base estática registro que apunta al inicio del
menos de 4 GB de memoria.
segmento global. ARM usa convencionalmente R9 como puntero de base estática (SB).

El segmento de datos dinámicos


El segmento de datos dinámicos sostiene la pila y el montón. Los datos de este
segmento no se conocen al inicio, pero se asignan y desasignan
dinámicamente durante la ejecución del programa.
Al iniciarse, el sistema operativo configura el puntero de la pila (SP) para que
apunte a la parte superior de la pila. La pila normalmente crece hacia abajo, como
se muestra aquí. La pila incluye almacenamiento temporal y variables locales, como
matrices, que no caben en los registros. Como se discutió en Sección
6.3.7 , las funciones también usan la pila para guardar y restaurar registros. Se accede a cada marco de
pila en el orden de último en entrar, primero en salir.
El montón almacena datos asignados por el programa durante el tiempo de ejecución. En
C, las asignaciones de memoria las realiza el malloc función; en C ++ y Java, nuevo se utiliza para
asignar memoria. Como un montón de ropa en el piso de un dormitorio, los datos del montón
se pueden usar y descartar en cualquier orden. El montón generalmente crece hacia arriba
desde la parte inferior del segmento de datos dinámicos.
Si la pila y el montón crecen entre sí, el programa ' Los datos de s pueden
dañarse. El asignador de memoria intenta asegurarse de que esto nunca
Grace Hopper, 1906 - 1992. suceda devolviendo un error de memoria insuficiente si no hay espacio
Graduado de la Universidad de Yale
suficiente para asignar datos más dinámicos.
con un Ph.D. en matemáticas.
Desarrolló el primer compilador El controlador de excepciones, el sistema operativo y los segmentos de E / S

mientras trabajaba para Remington La parte más baja del mapa de memoria ARM está reservada para la tabla de vectores de
Rand Corporation y fue fundamental excepción y los controladores de excepciones, comenzando en la dirección 0x0 (consulte Sección
en el desarrollo del lenguaje de 6.6.3 ). La parte más alta del mapa de memoria está reservada para el sistema operativo y
programación COBOL.
las E / S con mapa de memoria (consulte la Sección 9.2).
Como oficial naval, recibió muchos
premios, incluida la Medalla de la Victoria
6. 5. 2 Compilacion
de la Segunda Guerra Mundial y la Medalla
del Servicio de Defensa Nacional. Un compilador traduce código de alto nivel a lenguaje ensamblador. Los ejemplos
de esta sección se basan en GCC, un compilador gratuito popular y ampliamente
utilizado, que se ejecuta en la computadora de placa única Raspberry Pi
6.5 Luces, cámara, acción: compilación, montaje y carga 341

Ejemplo de código 6.29 COMPILACIÓN DE UN PROGRAMA DE ALTO NIVEL

Código de alto nivel Código de ensamblaje ARM

int f, g, y; // variables globales . texto En el ejemplo de código 6.29, se


. suma global accede a las variables globales
. tipo suma,% función
mediante dos instrucciones de
int suma (int a, int b) { suma:
retorno (a + b); agregar r0, r0, r1 memoria: una para cargar el dirección
} bx lr de la variable, y un segundo para
. principal global
. tipo función principal
leer o escribir la variable. Las
int main (vacío) principal: direcciones de la global
{ empujar {r3, lr}
las variables se colocan después del código,
f = 2; mov r0, # 2
g = 3; ldr r3, .L3 comenzando en la etiqueta
y = suma (f, g); str r0, [r3, # 0] . L3. LDR R3, .L3 carga el
return y; mov r1, # 3
dirección de F dentro R3, y
} ldr r3, .L3 + 4
str r1, [r3, # 0] STR R0, [R3, # 0] escribe a
suma
licenciado en Derecho
F; LDR R3, .L3 + 4 carga el
ldr r3, .L3 + 8
dirección de gramo dentro R3, y
str r0, [r3, # 0]
música pop {r3, pc} STR R1, [R3, # 0] escribe a
. L3:
gramo, y así. Sección 6.6.1
. palabra F
. palabra gramo
describe esta construcción de código
. palabra y ensamblador con más detalle.

(consulte la Sección 9.3). El ejemplo de código 6.29 muestra un programa simple de


alto nivel con tres variables globales y dos funciones, junto con el código
ensamblador producido por GCC.
Para compilar, ensamblar y vincular un programa en C llamado prog.c con
GCC, use el comando:

gcc - O1 - g prog.c - o prog

Este comando produce un archivo de salida ejecutable llamado prog. El - O1


flag le pide al compilador que realice optimizaciones básicas en lugar de producir un código
extremadamente ineficiente. El - gramo La bandera le dice al compilador que incluya información
de depuración en el archivo.
Para ver los pasos intermedios, podemos usar GCC - S bandera para compilar pero
no ensamblar ni vincular.

gcc - O1 - S prog.c - o prog.s

La salida, prog.s, es bastante detallado, pero las partes interesantes se muestran en el ejemplo
de código 6.29. Tenga en cuenta que GC requiere que las etiquetas vayan seguidas de dos
puntos. La salida de GCC está en minúsculas y tiene otras directivas de ensamblador que no se
describen aquí. Observa eso suma regresa usando el BX instrucción en lugar de
MOV PC, LR. Además, observe que GCC eligió guardar y restaurar R3 a pesar de que no es
uno de los registros conservados. Las direcciones de las variables globales se
almacenarán en una tabla que comienza en la etiqueta .L3.
342 CAPITULO SEIS Arquitectura

6. 5. 3 Montaje

Un ensamblador convierte el código en lenguaje ensamblador en un archivo de objeto que


contiene código de lenguaje de máquina. GCC puede crear el archivo de objeto desde
prog.s o directamente de prog.c usando

gcc - c prog.s - o prog.o

gcc - O1 - gramo - c prog.c - o prog.o

El ensamblador realiza dos pasadas por el código ensamblador. En la primera pasada, el


ensamblador asigna direcciones de instrucción y encuentra todos los símbolos, como
etiquetas y nombres de variables globales. Los nombres y direcciones de los símbolos se
guardan en una tabla de símbolos. En la segunda pasada por el código, el ensamblador
produce el código en lenguaje de máquina. Las direcciones de las etiquetas se toman de
la tabla de símbolos. El código de lenguaje de máquina y la tabla de símbolos se
almacenan en el archivo de objeto.
Podemos desmontar el archivo de objeto usando el objdump comando para ver el
código en lenguaje ensamblador junto al código en lenguaje máquina. Si el código se
compiló originalmente con - gramo, el desensamblador también muestra las líneas
correspondientes de código C:

objdump - S prog.o

A continuación se muestra el desmontaje de la sección .text:

00000000 <suma>:
int suma (int a, int b) {
retorno (a + b);
}
0: e0800001 agregar r0, r0, r1
4: e12fff1e bx lr

00000008 <principal>:

int f, g, y; // variables globales int sum (int a,

int b);

int main (void) {


8: e92d4008 empujar {r3, lr}
f = 2;
Recordar de Sección 6.4.6 que una
C: e3a00002 mov r0, # 2
lectura en PC devuelve la dirección de 10: e59f301c ldr r3, [pc, # 28] ; 34 <principal + 0x2c>
la instrucción actual más 8. Entonces, 14: e5830000 str r0, [r3]
LDR R3, [PC, n.º 28] cargas F' s g = 3;
18: e3a01003 mov r1, # 3
dirección, que está justo después
1c: e59f3014 ldr r3, [pc, # 20] ; 38 <principal + 0x30>
del código en: (PC + 8) + 28 = (0x10 20: e5831000 str r1, [r3]
+ 0x8) + 0x1C = 0x34. y = suma (f, g);
24: ebfffffe licenciado en 0 <suma>
Derecho
6.5 Luces, cámara, acción: compilación, montaje y carga 343

28: e59f300c ldr r3, [pc, # 12]; 3c <principal + 0x34> r0, [r3]
2c: e5830000 str
return y;
}
30: e8bd8008 pop {r3, pc}
...

También podemos ver la tabla de símbolos desde el archivo de objeto usando objdump con el - t bandera.
Las partes interesantes se muestran a continuación. Observe que el suma
La función comienza en la dirección 0 y tiene un tamaño de 8 bytes. principal comienza en la
dirección 8 y tiene un tamaño de 0x38. Los símbolos de variable global f, g, y h se enumeran y
tienen 4 bytes cada uno, pero aún no se les han asignado direcciones.

objdump - t prog.o

TABLA DE SÍMBOLOS:
00000000 ld .text 00000000 .texto
00000000 ld .data 00000000 .data
00000000 g F .text 00000008 suma
00000008 g F .text 00000038 principal
00000004 O * COM * 00000004 f
00000004 O * COM * 00000004 g
00000004 O * COM * 00000004 años

6. 5. 4 Enlace

La mayoría de los programas grandes contienen más de un archivo. Si el programador


cambia solo uno de los archivos, sería un desperdicio volver a compilar y volver a
ensamblar los otros archivos. En particular, los programas suelen llamar a funciones en
archivos de biblioteca; estos archivos de biblioteca casi nunca cambian. Si no se cambia
un archivo de código de alto nivel, no es necesario actualizar el archivo objeto asociado.
Además, un programa generalmente implica algún código de inicio para inicializar la pila,
el montón, etc., que debe ejecutarse antes de llamar al principal función.
El trabajo del enlazador es combinar todos los archivos de objeto y el código de
inicio en un archivo de lenguaje de máquina llamado ejecutable y asignar direcciones
para variables globales. El vinculador reubica los datos y las instrucciones en los archivos
de objeto para que no estén todos uno encima del otro. Utiliza la información de las
tablas de símbolos para ajustar el código en función de la nueva etiqueta y las
direcciones de las variables globales. Invoque GCC para vincular el archivo de objeto
usando:

gcc prog.o - o prog

Podemos volver a desmontar el ejecutable usando:

objdump - S -t prog

El código de inicio es demasiado extenso para mostrarlo, pero nuestro programa comienza en la
dirección 0x8390 en el segmento de texto y las variables globales tienen direcciones asignadas.
344 CAPITULO SEIS Arquitectura

a partir de 0x10570 en el segmento global. Observe la . palabra Directivas de


ensamblador que definen las direcciones de las variables globales. f, g, y y.

00008390 <suma>:

int suma (int a, int b) {


retorno (a + b);
}
8390: e0800001 agregar r0, r0, r1
8394: e12fff1e bx lr
00008398 <principal>:
int f, g, y; // variables globales int sum (int a,
int b);
int main (void) {
8398: e92d4008 presione {r3, lr}
f = 2;
839c: e3a00002 mov r0, # 2
83a0: e59f301c ldr r3, [pc, # 28]; 83c4 <principal + 0x2c> str r0, [r3]
La instrucción LDR R3, [PC,
83a4: e5830000
# 28] en las cargas ejecutables desde g = 3;
la dirección (PC + 8) + 28 = (0x83A0 + 83a8: e3a01003 mov r1, # 3
0x8) + 0x1C = 0x83C4. Esta dirección 83ac: e59f3014 ldr r3, [pc, # 20]; 83c8 <principal + 0x30> str r1, [r3]
de memoria contiene el valor 83b0: e5831000
0x10570, la ubicación de la variable y = suma (f, g);
global F. 83b4: ebfffff5 bl 8390 <sum>
83b8: e59f300c ldr r3, [pc, # 12]; 83cc <principal + 0x34> str r0, [r3]
83bc: e5830000
return y;
}
83c0: e8bd8008 música pop {r3, pc}
83c4: 00010570 . palabra 0x00010570
83c8: 00010574 . palabra 0x00010574
83cc: 00010578 . palabra 0x00010578

El ejecutable también contiene una tabla de símbolos actualizada con las


direcciones reubicadas de las funciones y variables globales.

TABLA DE SÍMBOLOS:
000082e4 ld .text 00010564 00000000 . texto
ld .data 00008390 g 00000000 . datos
F .texto 00000008 suma
00008398 g F .texto 00000038 principal
00010570 g O .bss 00000004 F
00010574 g O .bss 00000004 gramo
00010578 g O .bss 00000004 y

6. 5. 5 Cargando

El sistema operativo carga un programa leyendo el segmento de texto del archivo


ejecutable desde un dispositivo de almacenamiento (generalmente el disco duro) en el
segmento de texto de la memoria. El sistema operativo salta al principio del programa
para comenzar a ejecutarse. Figura 6.31 muestra el mapa de memoria al comienzo de la
ejecución del programa.
6.6 Probabilidades y finales 345

Dirección Memoria

SO y E / S

0xBEFFFAE8 Apilar SP

y
gramo

0x00010570 F

0x00010578
0x00010574 Figura 6.31 Ejecutable cargado en
0x00010570 memoria
0xE8BD8008
0xE5830000
0xE59F300C
0xEBFFFFF5
0xE5831000
0xE59F3014
0xE3A01003
0xE5830000
0xE59F301C
0xE3A00002
0xE92D4008 PC = 0x00008398
0xE12FFF1E
0x00008390 0xE0800001

Excepción
Manipuladores

6.6 RETAZOS*
Esta sección cubre algunos temas opcionales que no encajan naturalmente en ninguna otra
parte del capítulo. Estos temas incluyen la carga de literales de 32 bits, NOP y excepciones.

6. 6. 1 Cargando literales

Muchos programas necesitan cargar literales de 32 bits, como constantes o direcciones. MOV
solo acepta una fuente de 12 bits, por lo que LDR La instrucción se utiliza para cargar
estos números desde un grupo literal en el segmento de texto. Los ensambladores ARM
aceptan cargas de la forma

LDR Rd, = literal


LDR Rd, = etiqueta
346 CAPITULO SEIS Arquitectura

El primero carga una constante de 32 bits especificada por literal, y el segundo carga
la dirección de una variable o puntero en el programa especificado por etiqueta.
En ambos casos, el valor a cargar se mantiene en un piscina literal, que es una parte del
segmento de texto que contiene literales. El grupo literal debe tener menos de 4096
bytes del LDR instrucción para que la carga se pueda realizar como LDR Rd, [PC,
# offset_to_literal]. El programa debe tener cuidado de ramificarse alrededor del
grupo literal porque la ejecución de literales sería absurda o peor.

El ejemplo de código 6.30 ilustra la carga de un literal. Como se muestra en


Figura 6.32 , supongamos que el LDR la instrucción está en la dirección 0x8110 y el
literal está en 0x815C. Recuerde que la lectura de la PC devuelve la dirección 8 bytes
más allá de la instrucción actual que se está ejecutando. Por tanto, cuando el
LDR se ejecuta, la lectura de la PC devuelve 0x8118. Por lo tanto, la LDR usa un
desplazamiento de 0x44 para encontrar el grupo literal: LDR R1, [PC, # 0x44].

Ejemplo de código 6.30 GRANDE INMEDIATA UTILIZANDO UNA PISCINA LITERAL

Código de alto nivel Código de ensamblaje ARM

int a = 0x2B9056F; ; R1 = a
LDR R1, = 0x2B9056F
...

Dirección Instrucciones / Datos

Literal
0000815C 0x02B9056F Piscina

...
Figura 6.32 Grupo literal de ejemplo
00008114 ...

00008110 LDR R1, [PC, # 0x44] ordenador personal

Memoria principal

Las pseudoinstrucciones no son


6. 6. 2 NOP
en realidad forman parte del conjunto de

instrucciones, pero son una forma abreviada de NOP es un mnemónico para " No operacion " y se pronuncia " no op. " Es un pseudoinstrucció
instrucciones o secuencias de instrucciones que eso no hace nada. El ensamblador lo traduce a
suelen utilizar los programadores y compiladores. El MOV R0, R0 ( 0xE1A00000). Los NOP son útiles para, entre otras cosas, lograr
ensamblador traduce las pseudoinstrucciones en
cierto retraso o alinear instrucciones.

una o más instrucciones reales.


6.6 Probabilidades y finales 347

6. 6. 3 Excepciones

Un excepción es como una llamada de función no programada que se bifurca a una nueva
dirección. Las excepciones pueden deberse al hardware o al software. Por ejemplo, el
procesador puede recibir una notificación de que el usuario presionó una tecla en un teclado. El
procesador puede detener lo que está haciendo, determinar qué tecla se presionó, guardarla
para referencia futura y luego reanudar el programa que se estaba ejecutando. Una excepción
de hardware de este tipo desencadenada por un dispositivo de entrada / salida (E / S), como un
teclado, a menudo se denomina interrumpir. Alternativamente, el programa puede encontrar
una condición de error como una instrucción indefinida. A continuación, el programa se
ramifica al código en el sistema operativo (SO), que puede optar por emular la instrucción no
implementada o terminar el programa infractor. Las excepciones de software a veces se
denominan trampas. Una forma particularmente importante de trampa es llamada al sistema, por
lo que el programa invoca una función en el sistema operativo que se ejecuta en un nivel de
privilegio superior. Otras causas de excepciones incluyen el restablecimiento y los intentos de
leer la memoria inexistente.
Como cualquier otra llamada de función, una excepción debe guardar la dirección de
retorno, saltar a alguna dirección, hacer su trabajo, limpiar después de sí misma y regresar al
programa donde lo dejó. Las excepciones usan un vector tabla para determinar
donde saltar a la manejador de excepciones y use registros bancarios para mantener
Conserve copias adicionales de los registros de claves para que no corrompan los registros del
programa activo. Las excepciones también cambian nivel de privilegio del programa, lo que
permite que el controlador de excepciones acceda a partes protegidas de la memoria.

Modos de ejecución y niveles de privilegios


Un procesador ARM puede operar en uno de varios modos de ejecución con diferentes niveles
de privilegios. Los diferentes modos permiten que se produzca una excepción en un
controlador de excepciones sin dañar el estado; por ejemplo, podría ocurrir una interrupción
mientras el procesador está ejecutando el código del sistema operativo en el modo Supervisor,
y podría ocurrir una excepción de cancelación posterior si la interrupción intenta acceder a una
dirección de memoria no válida. Los manejadores de excepciones eventualmente regresarían y
reanudarían el código de supervisor. El modo se especifica en los bits inferiores del Registro de
estado del programa actual (CPSR), como se muestra en Figura 6.6 . Cuadro 6.13 enumera los
modos de ejecución y sus codificaciones. El modo de usuario opera en el nivel de privilegio PL0,
que no puede acceder a partes protegidas de la memoria, como el código del sistema
operativo. Los otros modos operan en el nivel de privilegio PL1, que puede acceder a todos los
recursos del sistema. Los niveles de privilegio son importantes para que el código de usuario
con errores o malintencionado no pueda corromper otros programas o bloquearse o infectar el
sistema.

Tabla de vectores de excepción

Cuando ocurre una excepción, el procesador se bifurca a un desplazamiento en el


tabla de vectores de excepciones, dependiendo de la causa de la excepción.
Cuadro 6.14 describe la tabla de vectores, que normalmente se encuentra a partir de la dirección
0x00000000 en la memoria. Por ejemplo, cuando ocurre una interrupción, el procesador se bifurca a la
dirección 0x00000018. Del mismo modo, en
348 CAPITULO SEIS Arquitectura

Cuadro 6.13 Modos de ejecución ARM

Modo CPSR 4: 0

Usuario 10000

Supervisor 10011

Abortar 10111

Indefinido 11011

Interrupción (IRQ) 10010

Interrupción rápida (FIQ) 10001

Cuadro 6.14 Tabla de vectores de excepciones

Excepción Dirección Modo

Reiniciar 0x00 Supervisor

Instrucción indefinida 0x04 Indefinido

Llamada de supervisor 0x08 Supervisor

Prefetch Abort (error de búsqueda de instrucción) Data 0x0C Abortar

Abort (error de carga o almacenamiento de datos) 0x10 Abortar

Reservado 0x14 N/A

Interrumpir 0x18 IRQ

Interrupción rápida 0x1C FIQ

ARM también admite un modo de


encendido, el procesador se ramifica a la dirección 0x00000000. Cada desplazamiento de
vectores altos en el que la tabla de
vector de excepción normalmente contiene una instrucción de bifurcación a un
vectores de excepción comienza en la
manejador de excepciones, código que maneja la excepción y luego sale o regresa al
dirección 0xFFFF0000. Para
código de usuario.
Por ejemplo, el sistema puede arrancar
usando una tabla de vectores en la ROM en Registros bancarios
la dirección 0x00000000. Una vez
Antes de que una excepción cambie la PC, debe guardar la dirección de retorno en el LR
el sistema se inicia, el sistema
para que el manejador de excepciones sepa dónde regresar. Sin embargo, debe tener
operativo puede escribir una tabla de
cuidado de no alterar el valor que ya está en el LR, que el programa necesitará más
vectores actualizada en la RAM en
adelante. Por lo tanto, el procesador mantiene un banco de registros diferentes para
0xFFFF0000 y poner el sistema en
usar como LR durante cada uno de los modos de ejecución. De manera similar, el
modo de vectores altos.
manejador de excepciones no debe alterar los bits del registro de estado.
6.6 Probabilidades y finales 349

Por lo tanto, un banco de registros de estado del programa guardados ( SPSR) se utiliza para guardar
una copia del CPSR durante las excepciones.
Si se produce una excepción mientras un programa está manipulando su marco de
pila, el marco puede estar en un estado inestable (por ejemplo, los datos se han escrito
en la pila pero el puntero de la pila aún no apunta a la parte superior de la pila). Por lo
tanto, cada modo de ejecución también usa su propia pila y copia almacenada de SP
apuntando a la parte superior de su pila. La memoria debe reservarse para la pila de
cada modo de ejecución y las versiones almacenadas de los punteros de la pila deben
inicializarse al inicio.
Lo primero que debe hacer un manejador de excepciones es empujar todos los
registros que podría cambiar a la pila. Esto lleva algo de tiempo. ARM tiene un interrupción
rápida modo de ejecución FIQ en el que R8 - Los R12 también se almacenan. Por lo tanto,
el manejador de excepciones puede comenzar inmediatamente sin guardar estos
registros.

Manejo de excepciones
Ahora que hemos definido los modos de ejecución, los vectores de excepción y los
registros bancarios, podemos definir qué ocurre durante una excepción. Al detectar
una excepción, el procesador:

1. Almacena el CPSR en el SPSR bancarizado

2. Establece el modo de ejecución y el nivel de privilegios según el tipo de excepción.

3 juegos máscara de interrupción bits en el CPSR para que el controlador de excepciones


no ser interrumpido

4. Almacena la dirección de devolución en el LR bancarizado.

5. Se ramifica a la tabla de vectores de excepción según el tipo de excepción.

A continuación, el procesador ejecuta la instrucción en la tabla de vectores de


excepciones, normalmente una rama al controlador de excepciones. El manejador
generalmente empuja otros registros a su pila, se encarga de la excepción y saca
los registros de la pila. El manejador de excepciones regresa usando el MOVS PC, LR instrucción,
un sabor especial de MOV que realiza la siguiente limpieza:

1. Copia el SPSR almacenado en el CPSR para restaurar el registro de estado

2. Copia el LR almacenado (posiblemente ajustado para ciertas excepciones) a


la PC para regresar al programa donde ocurrió la excepción

3. Restaura el modo de ejecución y el nivel de privilegios.

Instrucciones relacionadas con excepciones

Los programas operan con un nivel de privilegio bajo, mientras que el sistema
operativo tiene un nivel de privilegio más alto. Para hacer la transición entre niveles
de forma controlada, el programa coloca argumentos en registros y emite un llamada
de supervisor SVC) instrucción, que genera una excepción y plantea la
350 CAPITULO SEIS Arquitectura

nivel de privilegio. El sistema operativo examina los argumentos y realiza la


función solicitada, y luego regresa al programa.
El sistema operativo y otros códigos que operan en PL1 pueden acceder a los registros
bancarios para los diversos modos de ejecución utilizando el SRA ( moverse para registrarse
desde el registro especial) y MSR ( pasar al registro especial desde el registro) instrucciones. Por
ejemplo, en el momento del arranque, el sistema operativo utilizará estas instrucciones para
inicializar las pilas para los controladores de excepciones.

Puesta en marcha

En el arranque, el procesador salta al vector de reinicio y comienza a ejecutar


cargador de arranque código en modo supervisor. El cargador de arranque normalmente configura el
sistema de memoria, inicializa el puntero de la pila y lee el sistema operativo desde el disco; luego
comienza un proceso de arranque mucho más largo en el sistema operativo. El sistema operativo
eventualmente cargará un programa, cambiará al modo de usuario sin privilegios y saltará al inicio del
programa.

6,7 EVOLUCIÓN DE LA ARQUITECTURA DE ARMAS

El procesador ARM1 fue desarrollado por primera vez por Acorn Computer en
Gran Bretaña para las computadoras BBC Micro en 1985 como una
actualización del microprocesador 6502 utilizado en muchas computadoras
personales de la época. A lo largo del año le siguió el ARM2, que entró en
producción en la computadora Acorn Archimedes. ARM era un acrónimo de
Máquina RISC Bellota. El producto implementó la versión 2 del conjunto de
instrucciones ARM (ARMv2). El bus de direcciones tenía solo 26 bits y los 6 bits
superiores de la PC de 32 bits se utilizaron para contener los bits de estado. La
arquitectura incluía casi todas las instrucciones descritas en este capítulo, incluido
el procesamiento de datos, la mayoría de cargas y almacenes, sucursales y
multiplicaciones.
ARM pronto extendió el bus de direcciones a 32 bits completos, moviendo los
A partir de ARMv7, el CPSR se
bits de estado a un Registro de estado del programa actual (CPSR) dedicado.
denomina Aplicación ARMv4, introducido en 1993, agregó cargas de media palabra y almacena y
Registro de estado del programa proporcionó cargas de media palabra y byte firmadas y sin firmar. Este es el núcleo
(APSR). del conjunto de instrucciones ARM moderno y es lo que hemos cubierto en este
capítulo.
El conjunto de instrucciones ARM ha experimentado muchas mejoras descritas
en las secciones siguientes. El exitoso procesador ARM7TDMI en 1995 introdujo el
conjunto de instrucciones Thumb de 16 bits en ARMv4T para mejorar la densidad
del código. ARMv5TE agregó procesamiento de señal digital (DSP) e instrucciones
de punto flotante opcionales. ARMv6 agregó instrucciones multimedia y mejoró el
conjunto de instrucciones Thumb. ARMv7 mejoró las instrucciones multimedia y de
coma flotante, renombrándolas como Advanced SIMD. ARMv8 introdujo una
arquitectura de 64 bits completamente nueva. Se han introducido varias otras
instrucciones de programación del sistema a medida que ha evolucionado la
arquitectura.
6.7 Evolución de la arquitectura ARM 351

6. 7. 1 Conjunto de instrucciones de pulgar

Las instrucciones de pulgar tienen 16 bits de longitud para lograr una mayor densidad de
código; Son idénticas a las instrucciones ARM normales, pero generalmente tienen
limitaciones, entre ellas:

▶ Acceda solo a los ocho registros inferiores

▶ Reutilizar un registro como origen y destino Admite

▶ inmediatos más breves

▶ Falta de ejecución condicional

▶ Escribe siempre las banderas de estado

Casi todas las instrucciones ARM tienen equivalentes en Thumb. Debido a que las instrucciones
son menos poderosas, se requieren más para escribir un programa equivalente. Sin embargo,
las instrucciones son la mitad de largas, lo que da un tamaño general del código Thumb de
aproximadamente el 65% del equivalente de ARM. El conjunto de instrucciones Thumb es
valioso no solo para reducir el tamaño y el costo de la memoria de almacenamiento de código,
sino también para permitir un bus económico de 16 bits a la memoria de instrucciones y para
reducir la energía consumida al obtener instrucciones de la memoria.

Los procesadores ARM tienen un registro de estado de conjunto de


instrucciones, ISETSTATE, que incluye un bit T para indicar si el procesador está en
modo normal (T = 0) o en modo Thumb (T = 1). Este modo determina cómo se
deben obtener e interpretar las instrucciones. El BX y BLX Las instrucciones de rama
alternan el bit T para entrar o salir del modo Pulgar.
La codificación de instrucciones de pulgar es más compleja e irregular que las
instrucciones ARM para empaquetar tanta información útil como sea posible en medias
palabras de 16 bits. Figura 6.33 muestra codificaciones para instrucciones comunes de Thumb.
Los bits superiores especifican el tipo de instrucción. Las instrucciones de procesamiento de
datos suelen especificar dos registros, uno de los cuales es tanto el primer origen como el
destino. Siempre escriben las banderas de estado. Sumas, restas y cambios pueden especificar
un corto inmediato. Las ramas condicionales especifican un código de condición de 4 bits y un
desplazamiento corto, mientras que las ramas incondicionales permiten un desplazamiento
El pulgar irregular
más largo. Tenga en cuenta que BX toma un identificador de registro de 4 bits para que pueda
codificación de conjuntos de instrucciones e
acceder al registro de enlace LR. Formas especiales de LDR, STR, AÑADIR, y SUB están definidos
instrucciones de longitud variable
para operar en relación con el puntero de pila SP (para acceder al marco de pila durante las
(1 o 2 medias palabras) son
llamadas a funciones). Otra forma especial de
características de 16 bits
LDR cargas relativas a la PC (para acceder a un grupo literal). Formas de AGREGAR y arquitecturas de procesador que
MOV puede acceder a los 16 registros. licenciado en Derecho siempre requiere dos medias palabras para debe incluir una gran cantidad de
especificar un destino de 22 bits. información en una breve palabra
Posteriormente, ARM refinó el conjunto de instrucciones Thumb y agregó una serie de instrucción. El
de instrucciones Thumb-2 de 32 bits para mejorar el rendimiento de las operaciones la irregularidad complica

comunes y permitir que cualquier programa se escriba en modo Thumb. decodificación de instrucciones.
352 CAPITULO SEIS Arquitectura

15 0
010000 funct Rm Rdn <función> S Rdn, Rdn, Rm (procesamiento de datos)

0 0 0 ASR LSR imm5 Rm Rd LSLS / LSRS / ASRS Rd, Rm, # imm5


000 1 1 1 SUB imm3 Rm Rd ADDS / SUBS Rd, Rm, # imm3 ADDS /
001 1 SUB Rdn imm8 SUBS Rdn, Rdn, # imm8
010 0 0 1 0 0 Rdn [3] Rm Rdn [2: 0] AÑADIR Rdn, Rdn, Rm

101 1 0 0 0 0 SUB imm7 AÑADIR / SUB SP, SP, # imm7

0 01 01 Rn imm8 CMP Rn, # imm8


0 01 00 Rd imm8 MOV Rd, # imm8

010 0 0 1 1 0 Rdn [3] Rm Rdn [2: 0] MOV Rdn, Salón

0 10 00 111 L Rm 0 00 Salón BX / BLX

11 01 cond imm8 B <segundo> imm8

11 100 imm8 B imm11


0 1 0 1 L BH Rm Rn Rd STR (B / H) / LDR (B / H) Rd, [Rn, Rm]
0 1 1 0 L imm5 Rn Rd STR / LDR Rd, [Rn, # imm5]
1 0 0 1 L Rd imm8 STR / LDR Rd, [SP, # imm8]
0 1 0 01 Rd imm8 LDR Rd, [PC, # imm8]
1 1 1 10 imm22 [21:11] 11111 imm22 [10: 0] BL imm22

Figura 6.33 Ejemplos de codificación de instrucciones de pulgar

Las instrucciones Thumb-2 se identifican porque sus 5 bits más significativos son
11101, 11110 o 11111. A continuación, el procesador obtiene una segunda media
palabra que contiene el resto de la instrucción. La serie de procesadores Cortex-M
opera exclusivamente en el estado Thumb.

6. 7. 2 Instrucciones DSP
La Transformada Rápida de
Los procesadores de señales digitales (DSP) están diseñados para manejar de manera eficiente
Fourier (FFT), el algoritmo DSP
algoritmos de procesamiento de señales como la Transformada Rápida de Fourier (FFT) y los
más común, es complicado y
crítico para el rendimiento. El filtros de Respuesta de Impulso Finito / Infinito (FIR / IIR). Las aplicaciones comunes incluyen
Las instrucciones DSP en arquitecturas de codificación y decodificación de audio y video, control de motores y reconocimiento de voz.
computadora están destinadas a realizar ARM proporciona una serie de instrucciones DSP para estos fines. Las instrucciones DSP
FFT eficientes, especialmente en datos incluyen multiplicar, sumar y multiplicar-acumular (MAC) - multiplica y suma el resultado a una
fraccionarios de 16 bits. suma corriente: suma = suma + src1 × src2. MAC es una característica que distingue los
conjuntos de instrucciones DSP de los conjuntos de instrucciones regulares. Se utiliza con
mucha frecuencia en los algoritmos DSP y duplica el rendimiento en relación con las
Las instrucciones básicas de
instrucciones de multiplicación y adición separadas. Sin embargo, MAC requiere especificar un
multiplicación, enumeradas en el Apéndice
registro adicional para contener la suma acumulada.
B, son parte de ARMv4. ARMv5TE agregó
las instrucciones matemáticas saturadas y Las instrucciones DSP a menudo operan con datos cortos (16 bits) que representan
multiplicaciones empaquetadas y muestras leídas desde un sensor por un convertidor de analógico a digital. Sin embargo, los
fraccionarias para admitir algoritmos DSP. resultados intermedios se mantienen con mayor precisión (por ejemplo, 32 o 64 bits)
6.7 Evolución de la arquitectura ARM 353

Cuadro 6.15 Tipos de datos DSP

Tipo Bit de signo Bits enteros Bits fraccionales

corto 1 15 0

corto sin firmar 0 dieciséis 0

largo 1 31 0

largo sin firmar 0 32 0

largo largo 1 63 0

sin firmar mucho tiempo 0 64 0

Q15 1 0 15

Q31 1 0 31

o saturado para evitar desbordes. En aritmética saturada, los resultados mayores


que el número más positivo se tratan como los más positivos y los resultados más
pequeños que los más negativos se tratan como los más negativos. Por ejemplo, en La aritmética saturada es una forma
aritmética de 32 bits, los resultados son mayores que 2 31 - 1 saturar a 2 31 - 1, y importante de degradar con gracia
resultados inferiores a - 2 31 saturar en - 2 31. Los tipos de datos DSP comunes se dan la precisión en los algoritmos DSP.
en Cuadro 6.15 . Los números en complemento a dos se indican con un bit de signo. Comúnmente,
Los tipos de 16, 32 y 64 bits también se conocen como mitad, soltero, y doble precisión, la aritmética de precisión simple es

que no debe confundirse con números de coma flotante de precisión simple y suficiente para manejar la mayoría de las

doble. Para mayor eficiencia, dos números de precisión media se empaquetan en entradas, pero los casos patológicos
pueden sobrepasar el rango de precisión
una sola palabra de 32 bits.
simple. Un desbordamiento provoca un
El entero los tipos vienen en sabores firmados y sin firmar con el bit de
cambio abrupto de signo en una respuesta
signo en el msb. Fraccionario los tipos (Q15 y Q31) representan un número
radicalmente incorrecta, que puede
fraccionario con signo; por ejemplo, Q31 abarca el rango [ - 1, 1 - 2 - 31] con un
parecerle al usuario un clic en una
paso de 2 - 31 entre números consecutivos. Estos tipos no están definidos en el secuencia de audio o un píxel de color
estándar C, pero son compatibles con algunas bibliotecas. Q31 se puede extraño en una secuencia de video.
convertir a Q15 mediante truncamiento o redondeo. En truncamiento, el Pasando a la aritmética de doble precisión
resultado Q15 es solo la mitad superior. Al redondear, se agrega 0x00008000
al valor Q31 y luego se trunca el resultado. Cuando un cálculo implica muchos previene el desbordamiento pero

pasos, el redondeo es útil porque evita acumular varios errores pequeños de degrada el rendimiento y

truncamiento en un error significativo. aumenta el consumo de energía


en el caso típico. La aritmética
ARM agregó un Q bandera a los registros de estado para indicar que se ha
saturada recorta el desbordamiento
producido un desbordamiento o saturación en las instrucciones DSP. Para
en el valor máximo o mínimo, que
aplicaciones donde la precisión es crítica, el programa puede borrar el Q marca
generalmente está cerca del valor
antes de un cálculo, realice el cálculo en precisión simple y verifique el Q bandera
deseado y causa poca inexactitud.
después. Si está configurado, se produjo un desbordamiento y el cálculo se puede
repetir con doble precisión si es necesario.
354 CAPITULO SEIS Arquitectura

La suma y la resta se realizan de forma idéntica independientemente del


formato que se utilice. Sin embargo, la multiplicación depende del tipo. Por
ejemplo, con números de 16 bits, el número 0xFFFF se interpreta como 65535
para unsigned short, - 1 para abreviar, y - 2 - 15 para los números Q15. Por lo
tanto, 0xFFFF × 0xFFFF tiene un valor muy diferente para cada representación
(4,294,836,225; 1; y 2 - 30, respectivamente). Esto conduce a diferentes
instrucciones para la multiplicación con y sin signo.
Un número Q15 A se puede ver como a × 2 - 15, dónde a es su interpretación en
el rango [ - 2 15, 2 15 - 1] como un número de 16 bits con signo. Por lo tanto, el producto
de dos números Q15 es:

A × B = a × B × 2 - 30 = 2 × a × B × 2 - 31

Esto significa que para multiplicar dos números Q15 y obtener un resultado Q31, haga
una multiplicación ordinaria con signo y luego duplique el producto. Luego, el producto
se puede truncar o redondear para volver a colocarlo en el formato Q15 si es necesario.

La rica variedad de instrucciones de multiplicar y multiplicar-acumular se


resume en Cuadro 6.16 . Los MAC requieren hasta cuatro registros: RdHi, RdLo,
Rn, y Rm. Para operaciones de doble precisión, RdHi y RdLo
contienen los 32 bits más y menos significativos, respectivamente. Por ejemplo,
UMLAL RdLo, RdHi, Rn, Rm calcula { RdHi, RdLo} = {RdHi, RdLo} +
Rn × Rm. Las multiplicaciones de media precisión vienen en varios sabores indicados
entre llaves para elegir los operandos de la mitad superior o inferior de la palabra, y en doble
formas en las que se multiplican las mitades superior e inferior. MAC que involucran
entradas de precisión media y un acumulador de precisión simple
( SMLA *, SMLAW *, SMUAD, SMUSD, SMLAD, SMLSD) establecerá el Q marcar si el
el acumulador se desborda. Las multiplicaciones de la palabra más significativa (MSW)
también vienen en formas con un R sufijo esa ronda en lugar de truncar.
Las instrucciones de DSP también incluyen adición saturada ( QADD) y restar QSUB) de
palabras de 32 bits que saturan los resultados en lugar de desbordarlos. También
incluyen QDADD y QDSUB, que duplica el segundo operando antes de sumarlo / restarlo al
primero con saturación; Pronto encontraremos estos valiosos en MAC fraccionales. Ellos
establecieron el Q marcar si se produce saturación.
Finalmente, las instrucciones de DSP incluyen LDRD y STRD que cargan y
almacenan un par de registros par / impar en una palabra doble de memoria de 64
bits. Estas instrucciones aumentan la eficiencia de mover valores de doble precisión
entre la memoria y los registros.
Cuadro 6.17 resume cómo utilizar las instrucciones DSP para multiplicar o MAC
varios tipos de datos. Los ejemplos asumen que los datos de media palabra están en la
mitad inferior de un registro y que la mitad superior es cero; usa el sabor T de SMUL
cuando los datos están en la parte superior. El resultado se almacena en R2 o en {R3, R2} para
obtener una doble precisión. Las operaciones fraccionarias (Q15 / Q31) duplican el resultado
utilizando adiciones saturadas para evitar el desbordamiento al multiplicar - 1 × - 1.
6.7 Evolución de la arquitectura ARM 355

Cuadro 6.16 Multiplicar y multiplicar-acumular instrucciones

Instrucción Función Descripción

La multiplicación ordinaria de 32 bits funciona tanto con signo como sin signo.

MUL 32 = 32 × 32 Multiplicar

MLA 32 = 32 + 32 × 32 32 = Multiplicar-acumular

MLS 32 - 32 × 32 Multiplicar-restar

unsigned long long = unsigned long × largo sin firmar

UMULL 64 = 32 × 32 Sin signo multiplicar largo

UMLAL 64 = 64 + 32 × 32 Sin signo multiplicar-acumular mucho

UMAAL 64 = 32 + 32 × 32 + 32 Sin firmar multiplicar-acumular-agregar largo

largo largo = largo × largo

SMULL 64 = 32 × 32 Firmado multiplicar largo

SMLAL 64 = 64 + 32 × 32 Firmado multiplicar-acumular mucho

Aritmética empaquetada: corta × corto

SMUL {BB / BT / TB / TT} 32 = 16 × 16 Multiplicar {bottom / top} firmado

SMLA {BB / BT / TB / TT} 32 = 32 + 16 × 16 64 = Firmado multiplicar-acumular {bottom / top}

SMLAL {BB / BT / TB / TT} 64 + 16 × 16 Firmado multiplicar-acumular largo {bottom / top}

Multiplicación fraccionada (Q31 / Q15)

SMULW {B / T} 32 = (32 × 16) >> 16 Multiplicar palabra-media palabra firmada {bottom / top}

Firmado multiplicar-agregar palabra-media palabra {bottom /


SMLAW {B / T} 32 = 32 + (32 × 16) >> 16 32 = (32 × top}

SMMUL {R} 32) >> 32 MSW firmado multiplicar {round}

SMMLA {R} 32 = 32 + (32 × 32) >> 32 32 = 32 - ( 32 MSW firmado multiplicar-acumular {ronda}

SMMLS {R} × 32) >> 32 MSW firmado multiplicar-restar {ronda}

largo o largo largo = corto × corto + corto × corto

SMUAD 32 = 16 × 16 + 16 × 16 32 = 16 Multiplicar-agregar doble firmado

SMUSD × 16 - 16 × 16 Multiplicar-restar dual con signo

SMLAD 32 = 32 + 16 × 16 + 16 × 16 32 = 32 + Firmado multiplicar-acumular dual

SMLSD 16 × 16 - 16 × 16 64 = 64 + 16 × 16 + Multiplicar-restar doble con signo

SMLALD 16 × 16 64 = 64 + 16 × 16 - 16 × 16 Multiplicar-acumular con signo doble largo Con

SMLSLD signo de multiplicar-restar doble largo


356 CAPITULO SEIS Arquitectura

Cuadro 6.17 Multiplicar y código MAC para varios tipos de datos

Primer operando Segundo operando Producto


(R0) (R1) (R3 / R2) Multiplicar MAC

corto corto corto SMULBB R2, R0, R1 LDR R3, SMLABB R2, R0, R1 LDR R3, =
= 0x0000FFFF 0x0000FFFF
Y R2, R3, R2 Y R2, R3, R2

corto corto largo SMULBB R2, R0, R1 SMLABB R2, R0, R1, R2

corto corto largo largo MOV R2, # 0 SMLALBB R2, R3, R0, R1
MOV R3, # 0
SMLALBB R2, R3, R0, R1

largo corto largo SMULWB R2, R0, R1 MUL SMLAWB R2, R0, R1, R2 MLA

largo largo largo R2, R0, R1 R2, R0, R1, R2 SMLAL R2, R3,

largo largo largo largo SMULL R2, R3, R0, R1 R0, R1

corto sin firmar corto sin firmar corto sin firmar MUL R2, R0, R1 MLA R2, R0, R1, R2 LDR R3, =
LDR R3, = 0x0000FFFF 0x0000FFFF
Y R2, R3, R2 Y R2, R3, R2

corto sin firmar corto sin firmar largo sin firmar MUL R2, R0, R1 MLA R2, R0, R1, R2 MLA

largo sin firmar corto sin firmar largo sin firmar MUL R2, R0, R1 R2, R0, R1, R2 MLA R2,

largo sin firmar largo sin firmar largo sin firmar MUL R2, R0, R1 R0, R1, R2

largo sin firmar


largo sin firmar largo sin firmar largo UMULL R2, R3, R0, R1 UMLAL R2, R3, R0, R1

Q15 Q15 Q15 SMULBB R2, R0, R1 SMLABB R2, R0, R1, R2 SSAT
QADD R2, R2, R2 R2, 16, R2
LSR R2, R2, n. ° 16

Q15 Q15 Q31 SMULBB R2, R0, R1 SMULBB R3, R0, R1


QADD R2, R2, R2 QDADD R2, R2, R3

Q31 Q15 Q31 SMULWB R2, R0, R1 SMULWB R3, R0, R1


QADD R2, R2, R2 QDADD R2, R2, R3

Q31 Q31 Q31 SMMUL R2, R0, R1 SMMUL R3, R0, R1


QADD R2, R2, R2 QDADD R2, R2, R3
6.7 Evolución de la arquitectura ARM 357

6. 7. 3 Instrucciones de punto flotante

El punto flotante es más flexible que los números de punto fijo preferidos en DSP y
facilita la programación. El punto flotante se usa ampliamente en gráficos,
aplicaciones científicas y algoritmos de control. La aritmética de punto flotante se
puede realizar con una serie de instrucciones ordinarias de procesamiento de
datos, pero es más rápida y consume menos energía si se utilizan instrucciones y
hardware dedicados de punto flotante.
El conjunto de instrucciones ARMv5 incluye instrucciones opcionales de punto
flotante. Estas instrucciones acceden al menos a 16 registros de doble precisión de
64 bits separados de los registros ordinarios. Estos registros también pueden
tratarse como pares de registros de precisión simple de 32 bits. Los registros se
denominan D0 - D15 como doble precisión o S0 - S31 como precisión simple. Para
ejemplo, VADD.F32 S2, S0, S1 y VADD.F64 D2, D0, D1 realizar solo
y adiciones de punto flotante de doble precisión, respectivamente. Instrucciones de
coma flotante, enumeradas en Cuadro 6.18 , tienen el sufijo. F32 o . F64 para indicar
coma flotante de precisión simple o doble.

Cuadro 6.18 Instrucciones de ARM de punto flotante

Instrucción Función

VABS Rd, Salón Rd = | Rm |

VADD Rd, Rn, Rm Rd = Rn + Rm

VCMP Rd, Salón Comparar y establecer indicadores de estado de punto

VCVT Rd, Salón flotante Convertir entre int y float

VDIV Rd, Rn, Rm Rd = Rn / Rm

VMLA Rd, Rn, Rm Rd = Rd + Rn * Rm Rd =

VMLS Rd, Rn, Rm Rd - Rn * Rm Rd = Rm o

VMOV Rd, Rm o #const VMUL constante Rd = Rn * Rm

Rd, Rn, Rm

VNEG Rd, Salón Rd = - Rm

VNMLA Rd, Rn, Rm Rd = - ( Rd + Rn * Rm) Rd = -

VNMLS Rd, Rn, Rm ( Rd - Rn * Rm) Rd = - Rn *

VNMUL Rd, Rn, Rm Rm

VSQRT Rd, Rm Rd = raíz cuadrada (Rm)

VSUB Rd, Rn, Rm Rd = Rn - Rm


358 CAPITULO SEIS Arquitectura

El MRC y MCR Las instrucciones se utilizan para transferir datos entre los
registros ordinarios y los registros del coprocesador de coma flotante.
ARM define el Registro de control y estado de coma flotante (FPSCR). Como el
registro de estado ordinario, contiene N, Z, C, y V banderas para operaciones de
punto flotante. También especifica modos de redondeo, excepciones y condiciones
especiales como desbordamiento, subdesbordamiento y división por cero. El VMRS y
VMSR Las instrucciones transfieren información entre un registro regular y el FPSCR.

6. 7. 4 Instrucciones de seguridad y ahorro de energía

Los dispositivos que funcionan con baterías ahorran energía al pasar la mayor parte del
tiempo en modo de suspensión. ARMv6K introdujo instrucciones para respaldar dichos
ahorros de energía. La espera de la interrupción ( WFI) La instrucción permite que el
procesador entre en un estado de bajo consumo hasta que se produzca una interrupción.
El sistema puede generar interrupciones basadas en eventos del usuario (como tocar una
pantalla) o en un temporizador periódico. La espera del evento WFE) La instrucción es
similar pero es útil en sistemas multiprocesador (vea la Sección 7.7.8) para que un
procesador pueda entrar en suspensión hasta que otro procesador lo notifique. Se
despierta durante una interrupción o cuando otro procesador envía un evento usando el SEV
instrucción.
ARMv7 mejora el manejo de excepciones para admitir la virtualización y la seguridad. En virtualización,
múltiples sistemas operativos pueden ejecutarse simultáneamente en el mismo procesador, sin darse
cuenta de los demás ' s existencia. Un hipervisor cambia entre los sistemas operativos. El hipervisor
opera en el nivel de privilegio PL2. Se invoca con una excepción de captura de hipervisor. Con

seguridad extensiones, el procesador define un estado seguro con medios de entrada limitados
y acceso restringido a partes seguras de la memoria. Incluso si un atacante compromete el
sistema operativo, el kernel seguro puede resistir la manipulación. Por ejemplo, el kernel
seguro puede usarse para desactivar un teléfono robado o para hacer cumplir la administración
de derechos digitales de manera que un usuario pueda ' t contenido duplicado protegido por
derechos de autor.

6. 7. 5 Instrucciones SIMD

El término SIMD (pronunciado " sim-dee ") representa instrucción única datos
múltiples, en el que una sola instrucción actúa sobre varios datos en paralelo. Una
aplicación común de SIMD es realizar muchas operaciones aritméticas breves a la
vez, especialmente para el procesamiento de gráficos. Esto también se llama lleno aritmética.

Los elementos de datos cortos suelen aparecer en el procesamiento de gráficos. Por


ejemplo, un píxel en una foto digital puede usar 8 bits para almacenar cada uno de los
componentes de color rojo, verde y azul. El uso de una palabra completa de 32 bits para
procesar uno de estos componentes desperdicia los 24 bits superiores. Es más,
6.7 Evolución de la arquitectura ARM 359

cuando los componentes de 16 píxeles adyacentes se empaquetan en una palabra cuádruple


de 128 bits, el procesamiento se puede realizar 16 veces más rápido. De manera similar, las
coordenadas en un espacio de gráficos tridimensionales generalmente se representan con
números de punto flotante de 32 bits (precisión simple). Cuatro de estas coordenadas se
pueden empaquetar en una palabra cuádruple de 128 bits.
La mayoría de las arquitecturas modernas ofrecen operaciones aritméticas
SIMD con registros SIMD amplios que empaquetan varios operandos más
estrechos. Por ejemplo, las instrucciones ARMv7 Advanced SIMD comparten los
registros de la unidad de punto flotante. Además, estos registros también se
pueden emparejar para actuar como ocho palabras cuádruples de 128 bits Q0 - Q7.
Los registros empaquetan varios valores enteros o de coma flotante de 8, 16, 32 o
64 bits. Las instrucciones tienen el sufijo. I8, .I16, .I32, .I64, .F32, o . F64 para indicar
cómo deben tratarse los registros.
Figura 6.34 muestra el VADD.I8 D2, D1, D0 vector add instrucción que opera en ocho
pares de enteros de 8 bits empaquetados en palabras dobles de 64 bits. similar VADD.I32
Q2, Q1, Q0 agrega cuatro pares de enteros de 32 bits empaquetados en palabras
cuádruples de 128 bits y VADD.F32, D2, D1, D0 agrega dos pares de números de coma
flotante de precisión simple de 32 bits empaquetados en palabras dobles de 64 bits.
Realizar aritmética empaquetada requiere modificar la ALU para eliminar acarreos entre
los elementos de datos más pequeños. Por ejemplo, llevar a cabo
de a 0 + B 0 no debe afectar el resultado de a 1 + B 1.
Las instrucciones avanzadas de SIMD comienzan con V. Incluyen los siguientes
categorías de ing:

▶ Funciones aritméticas básicas también definidas para coma flotante

▶ Cargas y almacenamiento de varios elementos, incluido el desintercalado y el


entrelazado

▶ Operaciones lógicas bit a bit

▶ Comparaciones

▶ Muchos tipos de cambios, sumas y restas con y sin saturación.

▶ Muchos tipos de instrucciones varias

▶ de MAC y multiplicar

63 56 55 48 47 40 39 32 31 24 23 15
dieciséis 87 0 Posición de bit

a7 a6 a5 a4 a3 a2 a1 a0 D0
Figura 6.34 Aritmética empaquetada:
+ B7 B6 B5 B4 B3 B2 B1 B0 D1
ocho adiciones simultáneas de 8 bits

a7+B7 a6+B6 a5+B5 a4+B4 a3+B3 a2+B2 a1+B1 a0+B0 D2


360 CAPITULO SEIS Arquitectura

ARMv6 también definió un conjunto más limitado de instrucciones SIMD que operan
en los registros regulares de 32 bits. Estos incluyen sumas y restas de 8 y 16 bits, e
instrucciones para empaquetar y descomprimir de manera eficiente bytes y medias
palabras en una palabra. Estas instrucciones son útiles para manipular datos de 16 bits
en código DSP.

6. 7. 6 Arquitectura de 64 bits

Las arquitecturas de 32 bits permiten que un programa acceda directamente como máximo a 2 32 bytes
= 4 GB de memoria. Los grandes servidores informáticos lideraron la transición a
arquitecturas de 64 bits que pueden acceder a grandes cantidades de memoria.
Siguieron las computadoras personales y luego los dispositivos móviles. Las
arquitecturas de 64 bits a veces también pueden ser más rápidas porque mueven más
información con una sola instrucción.
Muchas arquitecturas simplemente extienden sus registros de propósito general de
32 a 64 bits, pero ARMv8 también introdujo un nuevo conjunto de instrucciones para
optimizar las idiosincrasias. El conjunto de instrucciones clásico carece de suficientes
registros de propósito general para programas complejos, lo que obliga al costoso
movimiento de datos entre los registros y la memoria. Mantener la PC en R15 y SP en R13
también complica la implementación del procesador, y los programas a menudo
necesitan un registro que contenga el valor 0.
Las instrucciones de ARMv8 todavía tienen una longitud de 32 bits y el
conjunto de instrucciones se parece mucho a ARMv7, pero con algunos problemas
resueltos. En ARMv8, el archivo de registro se expande a 31 registros de 64 bits
(llamados X0 - X30) y el PC y el SP ya no forman parte de los registros de propósito
general. X30 sirve como registro de enlace. Tenga en cuenta que no hay registro
X31; en cambio, se llama registro cero (ZR) y está cableado a 0. Las instrucciones de
procesamiento de datos pueden operar en valores de 32 o 64 bits, mientras que las
cargas y los almacenes siempre usan direcciones de 64 bits. Para dejar espacio para
los bits adicionales para especificar los registros de origen y destino, el campo de
condición se elimina de la mayoría de las instrucciones. Sin embargo, las ramas aún
pueden ser condicionales. ARMv8 también agiliza el manejo de excepciones, duplica
el número de registros SIMD avanzados y agrega instrucciones para la criptografía
AES y SHA. Las codificaciones de instrucciones son bastante complejas y no se
clasifican en un puñado de categorías.
Al reiniciar, los procesadores ARMv8 se inician en modo de 64 bits. El procesador
puede pasar al modo de 32 bits estableciendo un bit en un registro del sistema e
invocando una excepción. Vuelve al modo de 64 bits cuando vuelve la excepción.

6,8 OTRA PERSPECTIVA: ARQUITECTURA x86


Casi todas las computadoras personales de hoy usan microprocesadores de arquitectura x86.
x86, también llamado IA-32, es una arquitectura de 32 bits desarrollada originalmente por Intel.
AMD también vende microprocesadores compatibles con x86.
6.8 Otra perspectiva: Arquitectura x86 361

La arquitectura x86 tiene una historia larga y complicada que se remonta a


1978, cuando Intel anunció el microprocesador 8086 de 16 bits. IBM seleccionó el
8086 y su primo, el 8088, para IBM ' s primeras computadoras personales. En 1985,
Intel introdujo el microprocesador 80386 de 32 bits, que era compatible con
versiones anteriores del 8086, por lo que podía ejecutar software desarrollado para
PC anteriores. Las arquitecturas de procesador compatibles con el 80386 se
denominan procesadores x86. Los procesadores Pentium, Core y Athlon son
procesadores x86 bien conocidos.
Varios grupos de Intel y AMD durante muchos años han introducido más
instrucciones y capacidades en la arquitectura anticuada. El resultado es mucho
menos elegante que ARM. Sin embargo, la compatibilidad del software es mucho
más importante que la elegancia técnica, por lo que x86 ha sido el de facto Estándar
de PC durante más de dos décadas. Cada año se venden más de 100 millones de
procesadores x86. Este enorme mercado justifica más de $ 5 mil millones de
investigación y desarrollo anualmente para continuar mejorando los procesadores.
x86 es un ejemplo de una arquitectura de Computadora de conjunto de
instrucciones complejas (CISC). A diferencia de las arquitecturas RISC como ARM, cada
instrucción CISC puede hacer más trabajo. Los programas para arquitecturas CISC
generalmente requieren menos instrucciones. Las codificaciones de instrucciones se
seleccionaron para que fueran más compactas, a fin de ahorrar memoria, cuando la RAM
era mucho más cara de lo que es hoy; las instrucciones son de longitud variable y suelen
tener menos de 32 bits. La compensación es que las instrucciones complicadas son más
difíciles de decodificar y tienden a ejecutarse más lentamente.
Esta sección presenta la arquitectura x86. El objetivo no es convertirlo en
un programador en lenguaje ensamblador x86, sino ilustrar algunas de las
similitudes y diferencias entre x86 y ARM. Creemos que es interesante ver
cómo funciona x86. Sin embargo, no se necesita nada del material de esta
sección para comprender el resto del libro. Las principales diferencias entre
x86 y ARM se resumen en Cuadro 6.19 .

Cuadro 6.19 Principales diferencias entre ARM y x86

Rasgo BRAZO x86

# de registros 15 propósito general 8, algunas restricciones a propósito

# de operandos 3 - 4 (2 - 3 fuentes, 1 destino) 2 (1 fuente, 1 fuente / destino)

ubicación del operando registros o inmediatos registros, inmediatos o memoria

tamaño del operando 32 bits 8, 16 o 32 bits

banderas de condición sí sí

tipos de instrucción sencillo simple y complicado

codificación de instrucciones fijo, 4 bytes variable, 1 - 15 bytes


362 CAPITULO SEIS Arquitectura

6. 8. 1 Registros x86
Byte 3

Byte 2

Byte 1

Byte 0
El microprocesador 8086 proporcionó ocho registros de 16 bits. Podría acceder por
EAX
separado a los ocho bits superior e inferior de algunos de estos registros. Cuando
0 HACHA

AH AL se introdujo el 80386 de 32 bits, los registros se ampliaron a 32 bits. Estos registros


se denominan EAX, ECX, EDX, EBX, ESP, EBP, ESI y EDI. Para compatibilidad con
ECX
versiones anteriores, los 16 bits inferiores y algunas de las porciones de 8 bits
1 CX
CH CL inferiores también se pueden utilizar, como se muestra en
Figura 6.35 .
EDX
Los ocho registros son casi, pero no del todo, de propósito general. Algunas
2 DX
DH DL instrucciones no pueden utilizar determinados registros. Otras instrucciones siempre
ponen sus resultados en ciertos registros. Como SP en ARM, ESP normalmente se reserva
EBX
para el puntero de pila.
3 BX
BH BL El contador del programa x86 se llama EIP (el puntero de instrucción
extendido). Al igual que el ARM PC, avanza de una instrucción a la siguiente o
ESP se puede cambiar con instrucciones de llamada de función y rama.
4 SP

6. 8. 2 Operandos x86
EBP
5 BP Las instrucciones ARM siempre actúan sobre registros o inmediatos. Se necesitan
instrucciones explícitas de carga y almacenamiento para mover datos entre la memoria y

ESI los registros. Por el contrario, las instrucciones x86 pueden operar en registros,
6 SI inmediatos o memoria. Esto compensa parcialmente el pequeño conjunto de registros.

EDI Las instrucciones ARM generalmente especifican tres operandos: dos fuentes y
7 DI un destino. Las instrucciones x86 especifican solo dos operandos. El primero es una
fuente. El segundo es tanto un origen como un destino. Por lo tanto, las
Figura 6.35 registros x86 instrucciones x86 siempre sobrescriben una de sus fuentes con el resultado.
Cuadro 6.20 enumera las combinaciones de ubicaciones de operandos en x86. Todas las
combinaciones son posibles excepto de memoria a memoria.

Cuadro 6.20 Ubicaciones de operandos

Origen Destino Fuente Ejemplo Sentido

Registrarse Registrarse agregar EAX, EBX EAX < - EAX + EBX

Registrarse inmediato añadir EAX, 42 EAX < - EAX + 42

Registrarse memoria agregar EAX, [20] EAX < - EAX + Mem [20] Mem [20] < -

memoria Registrarse añadir [20], EAX Mem [20] + EAX Mem [20] < - Mem

memoria inmediato añadir [20], 42 [20] + 42


6.8 Otra perspectiva: Arquitectura x86 363

Cuadro 6.21 Modos de direccionamiento de memoria

Ejemplo Sentido Comentario

agregar EAX, [20] EAX < - EAX + Mem [20] desplazamiento

agregar EAX, [ESP] EAX < - EAX + Mem [ESP] EAX < - EAX + Mem direccionamiento base

agregar EAX, [EDX + 40] [EDX + 40] EAX < - EAX + Mem [60 + EDI * 4] base + desplazamiento

agregar EAX, [60 + EDI * 4] EAX < - EAX + Mem [EDX + 80 + EDI * 2] desplazamiento + índice escalado

agregar EAX, [EDX + 80 + EDI * 2] base + desplazamiento + índice escalado

Cuadro 6.22 Instrucciones que actúan sobre datos de 8, 16 o 32 bits

Ejemplo Sentido Tamaño de datos

agregar AH, BL AH < - AH + BL 8 bits

agregar AX, - 1 AX < - AX + 0xFFFF 16 bits

agregar EAX, EDX EAX < - EAX + EDX 32 bits

Al igual que ARM, x86 tiene un espacio de memoria de 32 bits direccionable por bytes. Sin
embargo, x86 admite una variedad más amplia de modos de indexación de memoria. Las
ubicaciones de memoria se especifican con cualquier combinación de un registro base,
desplazamiento, y un registro de índice escalado. Cuadro 6.21 ilustra estos
combinaciones. El desplazamiento puede ser un valor de 8, 16 o 32 bits. La escala
que multiplica el registro de índice puede ser 1, 2, 4 u 8. El modo base +
desplazamiento es equivalente al modo de direccionamiento base ARM para cargas
y tiendas. Al igual que ARM, x86 también proporciona un índice escalado. En x86, el
índice escalado proporciona una manera fácil de acceder a matrices o estructuras
de elementos de 2, 4 u 8 bytes sin tener que emitir una secuencia de instrucciones
para generar la dirección.
Si bien ARM siempre actúa en palabras de 32 bits, las instrucciones x86 pueden
operar en datos de 8, 16 o 32 bits. Cuadro 6.22 ilustra estas variaciones.

6. 8. 3 Indicadores de estado

x86, como muchas arquitecturas CISC, usa indicadores de condición (también llamados banderas
BRAZO ' El uso de indicadores de
de estado) para tomar decisiones sobre las ramas y realizar un seguimiento de los
condición lo distingue de otras
acarreos y el desbordamiento aritmético. x86 usa un registro de 32 bits, llamado EFLAGS,
arquitecturas RISC.
que almacena los indicadores de estado. Algunos de los bits del registro EFLAGS se dan
en Cuadro 6.23 . El sistema operativo utiliza otros bits.
364 CAPITULO SEIS Arquitectura

Cuadro 6.23 EFLAGS seleccionados

Nombre Sentido

CF ( Llevar bandera) Realización generada por última operación aritmética. Indica


desbordamiento en aritmética sin firmar. También se utiliza
para propagar el acarreo entre palabras en aritmética de
precisión múltiple.

ZF ( Bandera cero) El resultado de la última operación fue cero

SF ( Bandera de signo) El resultado de la última operación fue negativo (msb = 1)

DE ( Bandera de desbordamiento) Desbordamiento de dos ' s complemento aritmética

El estado arquitectónico de un procesador x86 incluye EFLAGS, así como


los ocho registros y el EIP.

6. 8. 4 Instrucciones x86

x86 tiene un conjunto de instrucciones más grande que ARM. Cuadro 6.24 describe
algunas de las instrucciones de propósito general. x86 también tiene instrucciones para
aritmética de punto flotante y para aritmética en varios elementos de datos cortos
empaquetados en una palabra más larga. D indica el destino (un registro o ubicación de
memoria), y S indica la fuente (un registro, ubicación de memoria o inmediata).
Tenga en cuenta que algunas instrucciones siempre actúan sobre registros
específicos. Por ejemplo, 32 × La multiplicación de 32 bits siempre toma una de las
fuentes de EAX y siempre coloca el resultado de 64 bits en EDX y EAX. CÍRCULO
siempre almacena el contador de bucle en ECX. PUSH, POP, CALL, y RETIRADO use el
puntero de pila, ESP.
Los saltos condicionales comprueban las banderas y se bifurcan si se cumple la
condición apropiada. Vienen en muchos sabores. Por ejemplo, JZ salta si la bandera cero ( ZF)
es 1. JNZ salta si el indicador de cero es 0. Al igual que ARM, los saltos suelen seguir una
instrucción, como la instrucción de comparación ( CMP), que pone las banderas. Cuadro
6.25 enumera algunos de los saltos condicionales y cómo dependen de las banderas
establecidas por una operación de comparación previa.

6. 8. 5 Codificación de instrucciones x86

Las codificaciones de instrucciones x86 son realmente desordenadas, un legado de décadas de


cambios parciales. A diferencia de ARMv4, cuyas instrucciones son uniformemente de 32 bits,
las instrucciones x86 varían de 1 a 15 bytes, como se muestra en Figura 6.36 . 1

1 Es posible construir instrucciones de 17 bytes si se utilizan todos los campos opcionales. Sin embargo, x86

establece un límite de 15 bytes en la longitud de las instrucciones legales.


6.8 Otra perspectiva: Arquitectura x86 365

Cuadro 6.24 Instrucciones x86 seleccionadas

Instrucción Sentido Función

AÑADIR / SUB sumar / restar D=D+S/D=D-S

ADDC agregar con llevar D = D + S + CF

INC / DIC incremento / decremento D=D+1/D=D-1

CMP comparar Establecer banderas basadas en D - SD

NEG negar =-D

Y / O / XOR AND / OR / XOR lógico D = D op S

NO NO lógico D=D

IMUL / MUL multiplicar firmado / no firmado EDX: EAX = EAX × D

IDIV / DIV división firmada / no firmada EDX: EAX / D


EAX = Cociente; EDX = Recordatorio

SAR / SHR desplazamiento aritmético / lógico a la derecha D = D >>> S / D = D >> SD = D

SAL / SHL Shift izquierdo << S

ROR / ROL rotar derecha / izquierda Girar D por S

RCR / RCL girar a la derecha / izquierda con la prueba Girar CF y D por S CF = D [S] ( la S th

BT de bit un poco de D) CF = D [S]; D [S] =

BTR / BTS prueba de bit y reset / set 0/1

PRUEBA establecer banderas basadas en el movimiento de Establecer banderas basadas en D Y

MOV bits enmascarados SD = S

EMPUJAR empujar hacia la pila ESP = ESP - 4; Mem [ESP] = SD = MEM

MÚSICA POP saltar de la pila [ESP]; ESP = ESP + 4 CF = 0/1

CLC, STC borrar / establecer bandera de transporte

JMP salto incondicional salto relativo: EIP = EIP + S


salto absoluto: EIP = S

Jcc salto condicional si (bandera) EIP = EIP + S

CÍRCULO círculo ECX = ECX - 1


si (ECX ≠ 0) EIP = EIP + imm

LLAMAR Llamada de función ESP = ESP - 4;


MEM [ESP] = EIP; EIP = S

RETIRADO función de retorno EIP = MEM [ESP]; ESP = ESP + 4


366 CAPITULO SEIS Arquitectura

Cuadro 6.25 Condiciones de rama seleccionadas

Instrucción Sentido Función después CMP D, S

JZ / JE saltar si ZF = 1 saltar si D = S

JNZ / JNE saltar si ZF = 0 saltar si D ≠ S

JGE saltar si SF = OF saltar si D ≥ S

JG saltar si SF = OF y ZF = 0 saltar si D> S

JLE saltar si SF ≠ DE o ZF = 1 saltar si D ≤ S

JL saltar si SF ≠ DE saltar si D <S

JC / JB saltar si CF = 1

JNC saltar si CF = 0

JO saltar si OF = 1

JNO saltar si OF = 0

JS saltar si SF = 1

JNS saltar si SF = 0

Prefijos Código de operación ModR / M HERMANO Desplazamiento Inmediato

Hasta 4 opcionales 1 byte 1 byte 1, 2 o 4 bytes para 1, 2 o 4 bytes para


1, 2 o 3 bytes
prefijos (por cierto (por cierto direccionamiento direccionamiento
código de operación
de 1 byte cada uno direccionamiento direccionamiento modos con modos con
Figura 6.36 codificaciones de modos) modos) desplazamiento inmediato
instrucciones x86

Reg /
Modificación
Código de operación
R/M Escala Índice Base
2 bits 3 bits 3 bits 2 bits 3 bits 3 bits

El código de operación puede ser de 1, 2 o 3 bytes. Le siguen cuatro campos opcionales:


ModR / M, SIB, Desplazamiento, y Inmediato. ModR / M especifica un
modo de direccionamiento. HERMANO especifica los registros de escala, índice y base en
ciertos modos de direccionamiento. Desplazamiento indica un desplazamiento de 1, 2 o 4 bytes
en ciertos modos de direccionamiento. Y Inmediato es una constante de 1, 2 o 4 bytes para
instrucciones que utilizan un inmediato como operando de origen. Además, una instrucción
puede ir precedida de hasta cuatro prefijos opcionales de bytes que modifican su
comportamiento.
El ModR / M byte usa el 2-bit Modificación y 3 bits R / M campo para especificar el modo de
direccionamiento para uno de los operandos. El operando puede provenir de
6.8 Otra perspectiva: Arquitectura x86 367

uno de los ocho registros, o de uno de los 24 modos de direccionamiento de memoria.


Debido a artefactos en las codificaciones, los registros ESP y EBP no están disponibles
para su uso como registro base o índice en ciertos modos de direccionamiento. El
Reg campo especifica el registro utilizado como el otro operando. Para ciertas
instrucciones que no requieren un segundo operando, el Reg El campo se usa para
especificar tres bits más del código de operación.
En los modos de direccionamiento que utilizan un registro de índice escalado, HERMANO byte
especifica el registro de índice y la escala (1, 2, 4 u 8). Si se utilizan tanto una base como un
índice, HERMANO byte también especifica el registro base.
ARM especifica completamente la instrucción en el cond, op, y funct campos de
la instrucción. x86 usa un número variable de bits para especificar diferentes
instrucciones. Utiliza menos bits para especificar instrucciones más comunes, lo
que reduce la longitud promedio de las instrucciones. Algunas instrucciones incluso
tienen varios códigos de operación. Por ejemplo, agregar AL, imm8 realiza una adición
de 8 bits de un inmediato a AL. Se representa con el código de operación de 1 byte,
0x04, seguido de un inmediato de 1 byte. El registro A (AL, AX o EAX) se llama acumulador.
Por otro lado, agregar D, imm8 realiza una adición de 8 bits de un inmediato a un
destino arbitrario, D ( memoria o un registro). Se representa con el 1 byte código de
operación 0x80 seguido de uno o más bytes especificando D, seguido de un valor
inmediato de 1 byte. Muchas instrucciones tienen codificaciones abreviadas cuando
el destino es el acumulador.

En el 8086 original, el código de operación especificó si la instrucción actuó en operandos


de 8 o 16 bits. Cuando el 80386 introdujo operandos de 32 bits, no había nuevos códigos de
operación disponibles para especificar la forma de 32 bits. En su lugar, se utilizó el mismo
código de operación para formularios de 16 y 32 bits. Un poco más en el descriptor de
segmento de código utilizado por el sistema operativo especifica qué forma debe elegir el
procesador. El bit se establece en 0 para compatibilidad con versiones anteriores de programas
8086, por lo que código de operación a operandos de 16 bits. Se establece en 1 para que los
programas usen operandos de 32 bits de forma predeterminada. Además, el programador
puede especificar prefijos para cambiar la forma de una instrucción en particular. Si el prefijo 0x66
aparece antes del código de operación,
se utiliza el operando de tamaño alternativo (16 bits en el modo de 32 bits o 32 bits en el
modo de 16 bits).

6. 8. 6 Otras peculiaridades x86

El 80286 introducido segmentación para dividir la memoria en segmentos de hasta


64 KB de longitud. Cuando el sistema operativo habilita la segmentación, las
direcciones se calculan en relación con el comienzo del segmento. El procesador
busca direcciones que van más allá del final del segmento e indica un error,
evitando así que los programas accedan a la memoria fuera de su propio
segmento. La segmentación resultó ser una molestia para los programadores y no
se utiliza en las versiones modernas del sistema operativo Windows.
368 CAPITULO SEIS Arquitectura

x86 contiene instrucciones de cadena que actúan sobre cadenas completas de bytes
Intel y Hewlett-Packard
o palabras. Las operaciones incluyen mover, comparar o escanear en busca de un valor
Desarrolló conjuntamente una
nueva arquitectura de 64 bits específico. En los procesadores modernos, estas instrucciones suelen ser más lentas que
llamada IA-64 a mediados de 1990 ' s. realizar la operación equivalente con una serie de instrucciones más simples, por lo que
Fue diseñado desde cero, sin pasar es mejor evitarlas.
por el enrevesado Como se mencionó anteriormente, el 0x66 prefijo se utiliza para elegir entre
historia de x86, tomando tamaños de operandos de 16 y 32 bits. Otros prefijos incluyen los que se usan para
ventaja de 20 años de nuevas bloquear el bus (para controlar el acceso a variables compartidas en un sistema
investigaciones en informática
multiprocesador), para predecir si se tomará una rama o no y para repetir la instrucción
arquitectura y proporcionar un espacio
durante un movimiento de cadena.
de direcciones de 64 bits.
La pesadilla de cualquier arquitectura es quedarse sin capacidad de memoria. Con
Sin embargo, IA-64 aún no se ha convertido
direcciones de 32 bits, x86 puede acceder a 4 GB de memoria. Esto era mucho más de lo que
en un éxito de mercado. La mayoría de las
tenían las computadoras más grandes en 1985, pero a principios de la década de 2000 se había
computadoras que necesitan un gran

espacio de direcciones ahora usan las


vuelto limitante. En 2003, AMD amplió el espacio de direcciones y los tamaños de registro a 64
extensiones de 64 bits de x86. bits, llamando a la arquitectura mejorada AMD64. AMD64 tiene un modo de compatibilidad que
le permite ejecutar programas de 32 bits sin modificaciones mientras el sistema operativo
aprovecha el espacio de direcciones más grande. En 2004, Intel cedió y adoptó las extensiones
de 64 bits, renombrándolas Tecnología de memoria extendida 64 (EM64T). Con direcciones de
64 bits, las computadoras pueden acceder a 16 exabytes (16 mil millones de GB) de memoria.

Para aquellos que tengan curiosidad por conocer más detalles de la arquitectura x86, el
desarrollador de software de arquitectura Intel x86 ' s Manual está disponible gratuitamente en
Intel ' s sitio web.

6. 8. 7 El panorama

Esta sección ha dado una idea de algunas de las diferencias entre la arquitectura
ARM logra un equilibrio entre
instrucciones simples
ARM RISC y la arquitectura x86 CISC. x86 tiende a tener programas más cortos,
y código denso al incluir características porque una instrucción compleja es equivalente a una serie de instrucciones ARM
tales como indicadores de condición y simples y porque las instrucciones están codificadas para minimizar el uso de
operandos de registro desplazados. memoria. Sin embargo, la arquitectura x86 es una mezcolanza de características
Características de Thease acumuladas a lo largo de los años, algunas de las cuales ya no son útiles pero
hacen que el código ARM sea deben conservarse para que sean compatibles con programas antiguos. Tiene muy
más compacto que otras pocos registros y las instrucciones son difíciles de decodificar. Simplemente explicar
arquitecturas RISC.
el conjunto de instrucciones es difícil. A pesar de todas estas fallas, x86 está
firmemente arraigado como la arquitectura de computadora dominante para PC,
porque el valor de la compatibilidad del software es muy grande y porque el
enorme mercado justifica el esfuerzo requerido para construir microprocesadores
x86 rápidos.

6,9 RESUMEN
Para controlar una computadora, debes hablar su idioma. Una arquitectura de computadora define
cómo controlar un procesador. Muchas arquitecturas informáticas diferentes tienen un uso comercial
generalizado en la actualidad, pero una vez
6.9 Resumen 369

entiendes uno, aprender otros es mucho más fácil. Las preguntas clave que debe
hacerse al abordar una nueva arquitectura son:

▶ ¿Cuál es la longitud de la palabra de

▶ datos? ¿Qué son los registros?

▶ ¿Cómo se organiza la memoria?

▶ ¿Cuáles son las instrucciones?

ARM es una arquitectura de 32 bits porque opera con datos de 32 bits. La


arquitectura ARM tiene 16 registros que incluyen 15 registros de propósito general y la
PC. En principio, cualquiera de los registros de propósito general se puede utilizar en
cualquier código. Sin embargo, por convención, ciertos registros están reservados para
ciertos propósitos para facilitar la programación y para que las funciones escritas por
diferentes programadores puedan comunicarse fácilmente. Por ejemplo, R14 (el registro
de enlace LR) contiene la dirección de retorno después de un licenciado en Derecho
instrucción y R0 - R3 contiene los argumentos de una función. ARM tiene un sistema de
memoria direccionable por bytes con direcciones de 32 bits. Las instrucciones tienen una
longitud de 32 bits y están alineadas por palabras para un acceso eficiente. Este capítulo
analizó las instrucciones ARM más utilizadas.
El poder de definir una arquitectura de computadora es que un programa escrito
para cualquier arquitectura dada puede ejecutarse en muchas implementaciones
diferentes de esa arquitectura. Por ejemplo, los programas escritos para el procesador
Intel Pentium en 1993 generalmente aún se ejecutarán (y se ejecutarán mucho más
rápido) en los procesadores Intel Xeon o AMD Phenom en 2015.
En la primera parte de este libro, aprendimos sobre el circuito y los niveles
lógicos de abstracción. En este capítulo, subimos al nivel de la arquitectura. En el
siguiente capítulo, estudiamos la microarquitectura, la disposición de los bloques
de construcción digitales que implementan una arquitectura de procesador. La
microarquitectura es el vínculo entre la ingeniería de hardware y software. Y
creemos que es uno de los temas más emocionantes de toda la ingeniería:
¡aprenderá a construir su propio microprocesador!
370 CAPITULO SEIS Arquitectura

Ejercicios
Ejercicio 6.1 Dé tres ejemplos de la arquitectura ARM de cada uno de los principios de
diseño de la arquitectura: (1) la regularidad apoya la simplicidad; (2) acelerar el caso
común; (3) más pequeño es más rápido; y (4) un buen diseño exige buenos
compromisos. Explique cómo cada uno de sus ejemplos exhibe el principio de diseño.

Ejercicio 6.2 La arquitectura ARM tiene un conjunto de registros que consta de 16 registros de
32 bits. ¿Es posible diseñar una arquitectura de computadora sin un conjunto de registros? Si es
así, describa brevemente la arquitectura, incluido el conjunto de instrucciones. ¿Cuáles son las
ventajas y desventajas de esta arquitectura sobre la arquitectura ARM?

Ejercicio 6.3 Considere el almacenamiento en memoria de una palabra de 32 bits almacenada en la palabra 42 de

memoria en una memoria direccionable por bytes.

(a) ¿Cuál es la dirección de bytes de la palabra de memoria 42?

(b) ¿Cuáles son las direcciones de bytes que abarca la palabra de memoria 42?

(c) Dibuje el número 0xFF223344 almacenado en la palabra 42 tanto en big-endian como


máquinas little-endian. Etiquete claramente la dirección de byte correspondiente a cada valor
de byte de datos.

Ejercicio 6.4 Repetir Ejercicio 6.3 para el almacenamiento en memoria de una palabra de 32 bits almacenada en la

palabra de memoria 15 en una memoria direccionable por bytes.

Ejercicio 6.5 Explique cómo se puede usar el siguiente programa ARM para determinar si
una computadora es big-endian o little-endian:

MOV R0, # 100


LDR R1, = 0xABCD876 ; R1 = 0xABCD876
STR R1, [R0]
LDRB R2, [R0, # 1]

Ejercicio 6.6 Escriba las siguientes cadenas con codificación ASCII. Escribe tus respuestas
finales en hexadecimal.

(a) SOS

(b) Genial

(c) ¡abucheo!

Ejercicio 6.7 Repetir Ejercicio 6.6 para las siguientes cadenas. (a)

hola

(b) leones

(c) ¡Al rescate!


Ejercicios 371

Ejercicio 6.8 Muestre cómo las cuerdas en Ejercicio 6.6 se almacenan en una memoria direccionable
por bytes en una máquina little-endian comenzando en la dirección de memoria 0x00001050C. Indique
claramente la dirección de memoria de cada byte.

Ejercicio 6.9 Repetir Ejercicio 6.8 para las cuerdas en Ejercicio 6.7 .

Ejercicio 6.10 Convierta el siguiente código ensamblador ARM en lenguaje de máquina. Escribe
las instrucciones en hexadecimal.

MOV R10, # 63488


LSL R9, R6, n.º 7
STR R4, [R11, R8] ASR
R6, R7, R3

Ejercicio 6.11 Repetir Ejercicio 6.10 para el siguiente código de ensamblaje ARM:

AÑADIR R8, R0, R1


LDR R11, [R3, # 4] SUB
R5, R7, # 0x58 LSL R3,
R2, # 14

Ejercicio 6.12 Considere las instrucciones de procesamiento de datos con un Src2.

(a) ¿Qué instrucciones de Ejercicio 6.10 están en este formato?

(b) Escriba el campo inmediato de 12 bits ( imm12) de las instrucciones de la parte


(a), luego escríbalos como inmediatos de 32 bits.

Ejercicio 6.13 Repetir Ejercicio 6.12 para las instrucciones en Ejercicio 6.11 .

Ejercicio 6.14 Convierta el siguiente programa del lenguaje máquina al lenguaje


ensamblador ARM. Los números de la izquierda son las direcciones de instrucción en la
memoria y los números de la derecha dan la instrucción en esa dirección. Luego aplica
ingeniería inversa a un programa de alto nivel que se compilaría en esta rutina de
lenguaje ensamblador y lo escribiría. Explique con palabras lo que hace el programa. R0 y
R1 son la entrada e inicialmente contienen números positivos, a y B. Al final del programa,
R0 es la salida.

0x00008008 0xE3A02000
0x0000800C 0xE1A03001
0x00008010 0xE1510000
0x00008014 0x8A000002
0x00008018 0xE2822001
0x0000801C 0xE0811003
0x00008020 0xEAFFFFFA
0x00008024 0xE1A00002
372 CAPITULO SEIS Arquitectura

Ejercicio 6.15 Repetir Ejercicio 6.14 para el siguiente código de máquina. R0 y R1 son las
entradas. R0 contiene un número de 32 bits y R1 es la dirección de una matriz de caracteres de
32 elementos (char).

0x00008104 0xE3A0201F
0x00008108 0xE1A03230
0x0000810C 0xE2033001
0x00008110 0xE4C13001
0x00008114 0xE2522001
0x00008118 0x5AFFFFFA
0x0000811C 0xE1A0F00E

Ejercicio 6.16 El NI La instrucción no es parte del conjunto de instrucciones ARM, porque la misma
funcionalidad se puede implementar usando instrucciones existentes. Escriba un fragmento de código
de ensamblaje corto que tenga la siguiente funcionalidad: R0 = R1 NOR R2. Utilice la menor cantidad de
instrucciones posible.

Ejercicio 6.17 El NAND La instrucción no es parte del conjunto de instrucciones ARM, porque la misma
funcionalidad se puede implementar usando instrucciones existentes. Escriba un breve fragmento de
código de ensamblaje que tenga la siguiente funcionalidad: R0 = R1 NAND R2. Utilice la menor cantidad
de instrucciones posible.

Ejercicio 6.18 Considere los siguientes fragmentos de código de alto nivel. Suponga las
variables enteras (con signo) gramo y h están en los registros R0 y R1, respectivamente.

(I) si (g> = h)
g = g + h;
demás
g = g - h;

(ii) si (g <h)
h = h + 1;
demás

h = h * 2;

(a) Escriba los fragmentos de código en lenguaje ensamblador ARM asumiendo que la ejecución condicional
está disponible solo para instrucciones de bifurcación. Utilice la menor cantidad de instrucciones posible
(dentro de estos parámetros).

(B) Escriba los fragmentos de código en lenguaje ensamblador ARM con ejecución condicional
disponible para todas las instrucciones. Utilice la menor cantidad de instrucciones posible.

(C) Compare la diferencia en la densidad del código (es decir, el número de instrucciones)
entre (a) y (b) para cada fragmento de código y analice las ventajas o desventajas.
Ejercicios 373

Ejercicio 6.19 Repetir Ejercicio 6.18 para los siguientes fragmentos de código.

(I) si (g> h)
g = g + 1;
demás
h = h - 1;

(ii) si (g <= h)
g = 0;
demás
h = 0;

Ejercicio 6.20 Considere el siguiente fragmento de código de alto nivel. Suponga que las
direcciones base de array1 y array2 se llevan a cabo en R1 y R2 y que array2 se inicializa
antes de su uso.

int i;
int array1 [100];
int array2 [100];
...
para (i = 0; i <100; i = i + 1)
matriz1 [i] = matriz2 [i];

(a) Escriba el fragmento de código en el ensamblaje ARM sin usar la indexación previa o posterior
o un registro escalado. Utilice la menor cantidad de instrucciones posible (dadas las limitaciones).

(b) Escriba el fragmento de código en el ensamblaje ARM con indexación previa o posterior y una
registro escalado disponible. Utilice la menor cantidad de instrucciones posible.

(c) Compare la diferencia en la densidad del código (es decir, el número de instrucciones) entre
(a y B). Analice las ventajas o desventajas.

Ejercicio 6.21 Repetir Ejercicio 6.20 para el siguiente fragmento de código de alto nivel. Asumir que temperatura
se inicializa antes de que se utilice y que R3 contiene la dirección base de temperatura

int i;
int temp [100];
...
para (i = 0; i <100; i = i + 1)
temp [i] = temp [i] * 128;

Ejercicio 6.22 Considere los siguientes dos fragmentos de código. Suponga que R1 se mantiene I
y que R0 tiene la dirección base del vals formación.

(I) int i;
int vals [200];

para (i = 0; i <200; i = i + 1)
vals [i] = i;
374 CAPITULO SEIS Arquitectura

(ii) int i;
int vals [200];

para (i = 199; i> = 0; i = i-1)


vals [i] = i;

(a) ¿Son los fragmentos de código funcionalmente equivalentes?

(b) Escriba cada fragmento de código utilizando lenguaje ensamblador ARM. Utilice tan pocas instrucciones
ciones como sea posible.

(c) Analice las ventajas o desventajas de un constructo sobre el otro.

Ejercicio 6.23 Repetir Ejercicio 6.22 para los siguientes fragmentos de código de alto nivel. Suponga
que R1 se mantiene I, R0 contiene la dirección base del nums matriz, y que la matriz se inicializa antes
de su uso.

(I) int i;
int nums [10];
...
para (i = 0; i <10; i = i + 1)
nums [i] = nums [i] / 2;

(ii) int i;
int nums [10];
...
para (i = 9; i> = 0; i = i-1)
nums [i] = nums [i] / 2;

Esta simple copia de cadena Ejercicio 6.24 Escribir una función en un lenguaje de alto nivel para int find42 (int matriz [],
La función tiene un defecto grave: int tamaño). Talla especifica el número de elementos en formación, y formación
no tiene forma de saber que especifica la dirección base del formación. La función debe devolver el número de índice de la
dst tiene suficiente espacio para recibir primera entrada de la matriz que contiene el valor 42. Si ninguna entrada de la matriz es 42,
src. Si un programador debe devolver el valor - 1.
malintencionado pudiera ejecutar strcpy
con una cuerda larga src, el
Ejercicio 6.25 La función de alto nivel strcpy copia la cadena de caracteres src a la cadena
programador podría escribir bytes en
de caracteres dst.
toda la memoria, posiblemente incluso
modificando el código almacenado en // código C
ubicaciones de memoria posteriores. void strcpy (char dst [], char src []) {
int i = 0;
Con algo de inteligencia, el código hacer {

modificado podría hacerse cargo de la dst [i] = src [i];

máquina. Esto se denomina ataque de


} while (src [i ++]);

desbordamiento del búfer; es }


empleado por varios programas
(a) Implementar el strcpy función en el código ensamblador ARM. Utilice R4 para I.
desagradables, incluido el
infame gusano Blaster, que causó (b) Haga un dibujo de la pila antes, durante y después de la strcpy Llamada de función.
daños estimados en $ 525 millones
en 2003. Suponga SP = 0xBEFFF000 justo antes strcpy se llama.
Ejercicios 375

Ejercicio 6.26 Convierta la función de alto nivel de Ejercicio 6.24 en código


ensamblador ARM.

Ejercicio 6.27 Considere el código de ensamblaje ARM a continuación. func1, func2, y func3
son funciones que no son hojas. func4 es una función de hoja. El código no se muestra para
cada función, pero los comentarios indican qué registros se utilizan dentro de cada función.

0x00091000 func1 ... ; func1 usa R4 - R10


0x00091020 BL func2
...
0x00091100 func2 ... ; func2 usa R0 - R5
0x0009117C BL func3
...
0x00091400 func3 ... ; func3 usa R3, R7 - R9
0x00091704 BL func4
...
0x00093008 func4 ... ; func4 usa R11 - R12
0x00093118 MOV PC, LR

(a) ¿Cuántas palabras son los marcos de pila de cada función?

(b) Dibuje la pila después func4 se llama. Indique claramente qué registros son
almacenado en la pila y marque cada uno de los marcos de la pila. Da valores siempre
que sea posible.

Ejercicio 6.28 Cada número de la serie de Fibonacci es la suma de los dos números
anteriores. Cuadro 6.26 enumera los primeros números de la serie, fib (n).

(a) ¿Qué es fib (n) por n = 0 y n = - 1?

(b) Escriba una función llamada mentira en un lenguaje de alto nivel que devuelve el Fibonacci
número para cualquier valor no negativo de norte. Sugerencia: probablemente desee utilizar un
bucle. Comente claramente su código.

(C) Convierta la función de alto nivel de la parte (b) en código ensamblador ARM. Agregue
comentarios después de cada línea de código que expliquen claramente lo que hace. Utilice el
simulador Keil MDK-ARM para probar su código en mentira( 9). (Consulte el Prefacio para saber
cómo instalar el simulador Keil MDK-ARM).

Cuadro 6.26 Serie de Fibonacci

norte 1 2 3 4 5 6 7 8 9 10 11 ...

fib (n) 1 1 2 3 5 8 13 21 34 55 89 ...


376 CAPITULO SEIS Arquitectura

Ejercicio 6.29 Considere el ejemplo de código 6.27. Para este ejercicio, asuma factorial (n)
se llama con argumento de entrada n = 5.

(a) ¿Qué valor está en R0 cuando factorial vuelve a la función de llamada?

(b) Suponga que reemplaza las instrucciones en las direcciones 0x8500 y 0x8520 por
PRESIONE {R0, R1} y POP {R1, R2}, respectivamente. ¿El programa:
(1) entrar en un bucle infinito pero no chocar;
(2) bloqueo (hace que la pila crezca o se reduzca más allá del segmento de datos
dinámicos o que la PC salte a una ubicación fuera del programa);
(3) producir un valor incorrecto en R0 cuando el programa vuelve al bucle (si es
así, ¿qué valor?); o
(4) ejecutar correctamente a pesar de las líneas eliminadas?

(C) Repita la parte (b) con las siguientes modificaciones de instrucciones:


(i) reemplace las instrucciones en las direcciones 0x8500 y 0x8520 con PRESIONE {R3,
LR} y POP {R3, LR}, respectivamente.
(ii) reemplace las instrucciones en las direcciones 0x8500 y 0x8520 con PRESIONE {LR}
y POP {LR}, respectivamente.
(iii) elimine la instrucción en la dirección 0x8510.

Ejercicio 6.30 Ben Bitdiddle está intentando calcular la función f (a, b) = 2 a + 3 B para no
negativo B. Se excede en el uso de llamadas a funciones y recursividad y produce el
siguiente código de alto nivel para funciones F y gramo.

// código de alto nivel para funciones f y g int f (int a, int


b) {
int j;

j = a;

devuelve j + a + g (b);
}
int g (int x) {
int k;
k = 3;

si (x == 0) devuelve 0; si no,
devuelve k + g (x - l);
}

Ben luego traduce las dos funciones al lenguaje ensamblador de la siguiente manera. También
escribe una función, prueba, que llama a la función f (5, 3).
Ejercicios 377

; Código de ensamblaje ARM


; f: R0 = a, R1 = b, R4 = j; ; g: R0 = x, R4
=k

Prueba 0x00008000 MOV R0, n.º 5 ;a=5


0x00008004 MOV R1, n.º 3 ;b=3
0x00008008 FDerecho
licenciado en ; llamar f (5, 3)
Bucle 0x0000800C B círculo ; y bucle para siempre
0x00008010 f EMPUJAR {R1, R0, LR, R4} ; guardar registros en la pila; j = a
0x00008014 MOV R4, R0
0x00008018 MOV R0, R1 ; coloque b como argumento para g;
0x0000801C licenciado en
gramo
Derecho llamar g (b)
0x00008020 MOV R2, R0 ; colocar el valor de retorno en R2
0x00008024 MÚSICA POP {R1, R0} ; restaurar ayb después de la llamada;
0x00008028 AGREGAR R0, R2, R0 R0 = g (b) + a
0x0000802C AGREGAR R0, R0, R4 ; R0 = (g (b) + a) + j; restaurar
0x00008030 MÚSICA POP {R4, LR} R4, LR
0x00008034 MOV PC, LR ; regreso
0x00008038 g EMPUJAR {R4, LR} ; guardar registros en la pila
0x0000803C MOV R4, n.º 3 ;k=3
0x00008040 CMP R0, # 0 ; x == 0?
0x00008044 BNE demás ; rama cuando no es igual
0x00008048 MOV R0, # 0 ; si es igual, devuelve valor = 0
0x0000804C B hecho ; y limpiar
0x00008050 más SUB R0, R0, n.º 1 ;x=x-1
0x00008054 licenciado en
gramo
Derecho ; llamar g (x - 1)
0x00008058 AGREGAR R0, R0, R4 ; R0 = g (x - 1) + k
0x0000805C hecho MÚSICA POP{R4, LR} ; restaurar R0, R4, LR de la pila; regreso
0x00008060 MOV PC, LR

EsFigura
Probablemente encontrarás en útil hacer dibujos
6.14 para de la
ayudarlo pila similares
a responder las al de
siguientes preguntas.

(a) Si el código se ejecuta a partir de prueba, ¿Qué valor hay en R0 cuando el programa obtiene
para ¿círculo? ¿Su programa calcula correctamente 2 a + 3 ¿B?

(B) Suponga que Ben cambia las instrucciones en las direcciones 0x00008010 y
0x00008030 a PRESIONE {R1, R0, R4} y POP {R4}, respectivamente. ¿El programa

(1) entrar en un bucle infinito pero no chocar;


(2) falla (hace que la pila crezca más allá del segmento de datos dinámicos o
PC para saltar a una ubicación fuera del programa);
(3) producir un valor incorrecto en R0 cuando el programa vuelve a círculo
(si es así, ¿qué valor?), o
(4) ¿se ejecuta correctamente a pesar de las líneas eliminadas?
378 CAPITULO SEIS Arquitectura

(c) Repita la parte (b) cuando se cambien las siguientes instrucciones. Tenga en cuenta que las etiquetas
aren ' t cambiado, solo instrucciones.
(I) instrucciones en 0x00008010 y 0x00008024 cambian a PRESIONE {R1, LR, R4} y POP
{R1}, respectivamente.
(ii) instrucciones en 0x00008010 y 0x00008024 cambian a PRESIONE {R0, LR, R4} y POP
{R0}, respectivamente.
(iii) las instrucciones en 0x00008010 y 0x00008030 cambian a PRESIONE {R1, R0,
LR} y POP {LR}, respectivamente.
(iv) se eliminan las instrucciones en 0x00008010, 0x00008024 y 0x00008030.
(v) las instrucciones en 0x00008038 y 0x0000805C cambian a PRESIONE {R4} y
POP {R4}, respectivamente.
(vi) las instrucciones en 0x00008038 y 0x0000805C cambian a PRESIONE {LR} y
POP {LR}, respectivamente.
(vii) se eliminan las instrucciones en 0x00008038 y 0x0000805C.

Ejercicio 6.31 Convierta las siguientes instrucciones de bifurcación en código de máquina. Las
direcciones de instrucción se dan a la izquierda de cada instrucción.

(a) 0x0000A000 BUCLE BEQ


0x0000A004 ...
0x0000A008 ...
0x0000A00C BUCLE ...

(B) 0x00801000 BGE HECHO


...
0x00802040 HECHO ...

(C) 0x0000B10C ATRÁS ...


... ...
0x0000D000 BHI VOLVER

(D) 0x00103000 BL FUNC


... ...
0x0011147C FUNC ...

(mi) 0x00008004 L1 ...


... ...
0x0000F00C B L1

Ejercicio 6.32 Considere el siguiente fragmento de lenguaje ensamblador de ARM. Los


números a la izquierda de cada instrucción indican la dirección de la instrucción.

0x000A0028 FUNC1 MOV R4, R1


0x000A002C AÑADIR R5, R3, R5, LSR # 2
0x000A0030 SUB R4, R0, R3, ROR R4
0x000A0034 BL FUNC2
... ...
0x000A0038 FUNC2 LDR R2, [R0, # 4]
0x000A003C STR R2, [R1, -R2]
Ejercicios 379

0x000A0040 CMP R3, # 0


0x000A0044 BNE ELSE
0x000A0048 MOV PC, LR
0x000A004C ELSE SUB R3, R3, n.º 1
0x000A0050 B FUNC2

(a) Traduzca la secuencia de instrucciones a código de máquina. Escribe la máquina


instrucciones de código en hexadecimal.

(b) Enumere el modo de direccionamiento utilizado en cada línea de código.

Ejercicio 6.33 Considere el siguiente fragmento de código C.

// código C
void setArray (int num) {
int i;
int matriz [10];

para (i = 0; i <10; i = i + 1)
matriz [i] = comparar (num, i);
}
int compare (int a, int b) {
si (sub (a, b)> = 0)
return 1;
demás
return 0;
}
int sub (int a, int b) {
devolver un - B;
}

(a) Implemente el fragmento de código C en lenguaje ensamblador ARM. Use R4 para


mantener la variable I. Asegúrese de manejar el puntero de la pila de forma adecuada. La
matriz se almacena en la pila del setArray función (ver el final de Sección 6.3.7 ).

(B) Asumir setArray es la primera función llamada. Dibuja el estado de la pila antes de llamar setArray
y durante cada llamada de función. Indique los nombres de los registros y variables
almacenados en la pila, marque la ubicación de SP y marque claramente cada marco de
pila.

(C) ¿Cómo funcionaría su código si no pudiera almacenar LR en la pila?

Ejercicio 6.34 Considere la siguiente función de alto nivel.

// código C
int f (int n, int k) {
int b;

b = k + 2;
si (n == 0) b = 10;
si no b = b + (n * n) + f (n - 1, k + 1); return b * k;

}
380 CAPITULO SEIS Arquitectura

(a) Traducir la función de alto nivel F en lenguaje ensamblador ARM. Preste especial atención
a guardar y restaurar correctamente los registros a través de llamadas a funciones y usar
las convenciones de registros preservados de ARM. Comente claramente su código.
Puedes usar el ARM MUL instrucción. La función comienza en la dirección de instrucción
0x00008100. Mantener la variable local B en R4.

(B) Repase manualmente su función del inciso a) para el caso de f (2, 4).
Haz un dibujo de la pila similar a la de Figura 6.14 , y suponga que SP es igual a
0xBFF00100 cuando F se llama. Escriba el nombre del registro y el valor de los datos
almacenados en cada ubicación de la pila y realice un seguimiento del valor del
puntero de la pila (SP). Marque claramente cada marco de pila. También puede
resultarle útil realizar un seguimiento de los valores en R0, R1 y R4 durante la
ejecución. Asume que cuando F se llama, R4 = 0xABCD y LR = 0x00008010. ¿Cuál es el
valor final de R0?

Ejercicio 6.35 Dé un ejemplo del peor de los casos para una bifurcación directa (es decir, una
bifurcación a una dirección de instrucción superior). El peor de los casos es cuando la rama no puede
ramificarse lejos. Muestre instrucciones y direcciones de instrucciones.

Ejercicio 6.36 Las siguientes preguntas examinan las limitaciones de la instrucción de


rama, B. Dé su respuesta en número de instrucciones relativas a la instrucción de rama.

(a) En el peor de los casos, ¿hasta dónde puede B bifurcar hacia adelante (es decir, a direcciones superiores)?

(El peor de los casos es cuando la instrucción de bifurcación no puede bifurcarse lejos). Explique
utilizando palabras y ejemplos, según sea necesario.

(B) En el mejor de los casos, ¿qué tan lejos B bifurcación hacia adelante? (El mejor caso es cuando la
instrucción de bifurcación puede bifurcar más lejos). Explique.

(C) En el peor de los casos, ¿qué tan lejos B bifurcar hacia atrás (a direcciones más bajas)? Explicar. En

(D) el mejor de los casos, ¿qué tan lejos B rama hacia atrás? Explicar.

Ejercicio 6.37 Explique por qué es ventajoso tener un campo inmediato grande,
imm24, en el formato de máquina para las instrucciones de bifurcación, B y LICENCIADO EN DERECHO.

Ejercicio 6.38 Escriba el código ensamblador que se bifurque a una instrucción 32


Minstructions desde la primera instrucción. Recuerde que 1 Minstrucción = 2 20

instrucciones = 1.048.576 instrucciones. Suponga que su código comienza en la dirección


0x00008000. Utilice un número mínimo de instrucciones.

Ejercicio 6.39 Escriba una función en código de alto nivel que tome una matriz de 10 entradas
de enteros de 32 bits almacenados en formato little-endian y la convierta a formato big-endian.
Después de escribir el código de alto nivel, conviértalo en código ensamblador ARM. Comente
todo su código y use un número mínimo de instrucciones.
Ejercicios 381

Ejercicio 6.40 Considere dos cadenas: cadena1 y cadena2.

(a) Escriba código de alto nivel para una función llamada concat que concatena (une
juntos) las dos cadenas: void concat (char string1 [], char string2 [], char stringconcat []). La
función no devuelve ningún valor. Se concate-
nates cadena1 y cadena2 y coloca la cadena resultante en stringconcat.
Puede suponer que la matriz de caracteres stringconcat es lo suficientemente grande para
acomodar la cadena concatenada.

(B) Convierta la función de la parte (a) al lenguaje ensamblador ARM.

Ejercicio 6.41 Escriba un programa de ensamblaje ARM que agregue dos números de punto flotante de
precisión simple positivos contenidos en R0 y R1. No utilice ninguna de las instrucciones de ARM de
punto flotante. No necesita preocuparse por ninguna de las codificaciones que están reservadas para
propósitos especiales (por ejemplo, 0, NAN, etc.) o números que se desbordan o se desbordan. Utilice
el simulador Keil MDK-ARM para probar su código. (Consulte el Prefacio para saber cómo instalar el
simulador Keil MDK-ARM.) Deberá configurar manualmente los valores de R0 y R1 para probar su
código. Demuestre que su código funciona de manera confiable.

Ejercicio 6.42 Considere el siguiente programa ARM. Suponga que las instrucciones se colocan
comenzando en la dirección de memoria 0x8400 y que L1 está en la dirección de memoria 0x10024.

; ARM código de ensamblaje


PRINCIPAL
PRESIONE {LR}
LDR R2, = L1; esto se traduce en una carga relativa a PC LDR R0, [R2]

LDR R1, [R2, # 4] BL


DIFF
POP {LR}
MOV PC, LR
DIFF
SUB R0, R0, R1
MOV PC, LR
...
L1

(a) Primero muestre la dirección de instrucción junto a cada instrucción de ensamblaje. (b)

Describa la tabla de símbolos: es decir, enumere la dirección de cada una de las etiquetas. (c)

Convierta todas las instrucciones en código de máquina.

(d) ¿Qué tamaño (cuántos bytes) son los segmentos de datos y texto?

(e) Dibuje un mapa de memoria que muestre dónde se almacenan los datos y las instrucciones,
Similar a Figura 6.31 .
382 CAPITULO SEIS Arquitectura

Ejercicio 6.43 Repetir Ejercicio 6.42 para el siguiente código ARM. Suponga que las instrucciones se
colocan comenzando en la dirección de memoria 0x8534 y que L2 está en la dirección de memoria
0x1305C.

; ARM código de ensamblaje


PRINCIPAL
PRESIONE {R4, LR}
MOV R4, n.º 15
LDR R3, = L2; esto se traduce en una carga relativa a PC STR
R4, [R3]
MOV R1, n.º 27
STR R1, [R3, # 4]
LDR R0, [R3]
licenciado en MAYOR
Derecho QUE
MÚSICA POP {R4, LR}
MOV PC, LR
MAYOR QUE
CMP R0, R1
MOV R0, # 0
MOVGT R0, # 1
MOV PC, LR
...
L2

Ejercicio 6.44 Nombre dos instrucciones ARM que pueden aumentar la densidad del código (es
decir, disminuir el número de instrucciones en un programa). Dé ejemplos de cada uno,
mostrando el código ensamblador ARM equivalente con y sin usar las instrucciones.

Ejercicio 6.45 Explique las ventajas y desventajas de la ejecución condicional.


Preguntas de entrevista 383

Preguntas de entrevista

Los siguientes ejercicios presentan preguntas que se han hecho en entrevistas para trabajos de
diseño digital (pero generalmente están abiertas a cualquier lenguaje ensamblador).

Pregunta 6.1 Escriba el código ensamblador ARM para intercambiar el contenido de dos
registros, R0 y R1. No puede utilizar ningún otro registro.

Pregunta 6.2 Suponga que se le da una matriz de números enteros positivos y negativos. Escriba el
código de ensamblaje ARM que encuentre el subconjunto de la matriz con la suma más grande.
Suponga que la matriz ' La dirección base y el número de elementos de la matriz están en R0 y R1,
respectivamente. Su código debe colocar el subconjunto resultante de la matriz comenzando en la
dirección base en R2. Escribe código que se ejecute lo más rápido posible.

Pregunta 6.3 Se le da una matriz que contiene una cadena C. La cadena forma una
oración. Diseñe un algoritmo para invertir las palabras en la oración y almacenar la
nueva oración nuevamente en la matriz. Implemente su algoritmo usando código
ensamblador ARM.

Pregunta 6.4 Diseñe un algoritmo para contar el número de 1 ' s en un número de 32


bits. Implemente su algoritmo usando código ensamblador ARM.

Pregunta 6.5 Escriba el código ensamblador ARM para invertir los bits en un registro. Utilice la menor cantidad
de instrucciones posible. Suponga que el registro de interés es R3.

Pregunta 6.6 Escriba el código de ensamblaje de ARM para probar si se produce un desbordamiento
cuando se agregan R2 y R3. Utilice un número mínimo de instrucciones.

Pregunta 6.7 Diseñe un algoritmo para probar si una determinada cadena es un palíndromo.
(Recuerde que un palíndromo es una palabra que es lo mismo hacia adelante y hacia atrás. Por
ejemplo, las palabras " Guau " y " coche de carreras " son palíndromos.) Implemente su algoritmo
usando el código ensamblador ARM

También podría gustarte