1 Programación a nivel de máquina Lenguaje
Ensamblador Introducción: Para dar ordenes al hardware una
computadora, debes hablar su idioma. Las palabras de un lenguaje
de máquina se llaman instrucciones y su vocabulario se
llama conjunto de instrucciones de la arquitectura (ISA). En este
capitulo se aprenderá el diseño y uso de un
conjunto de instrucciones de una máquina real, en la forma
escrita por las personas y en la forma que la maquina
“entiende”.
2 El modelo de ejecución Estado Visible por el
programador: PC: Contador de programa –dirección de la
próxima instrucción Banco de Registros:
-Fuertemente usado por los datos de programa. Códigos de
condición -Almacena información de estado de las
operaciones realizadas recientemente. Memoria: -Arreglo accesible
por bytes Código, datos de usuario, datos de S.O. -Pila
(Stack): memoria utilizada para soportar procedimientos
3 Convirtiendo código C a código objeto
Código: archivos de texto Compila con el comando: -gcc
p1.c, p2.c -o p.exe Optimizaciones: – O -O0: -O1: -O2: Enlace
(Link) -Enlaza programas p1.o, p2.o con librerías
estáticas. Salida (-o p.exe) -Pone el programa binario en
el archivo p.exe .
Características del lenguaje ensamblador SubTipos de datos
reducido: Datos de enteros de 1, 2 ó 4 bytes Valores de
datos Direcciones (apuntadores sin tipo) Datos de Punto Flotante
4, 8 ó 16 bytes No agrega tipos como arreglos o
estructuras Asigna bytes en direcciones contiguas de memoria
¡El programador tiene el control total de la
máquina! 4
5 Características del lenguaje ensamblador SubOperaciones
primarias: Realiza funciones aritméticas sobre valores
almacenados en registros o sobre valores almacenados en memoria.
ADD, SUB, MUL, etc. Transfiere datos entre registros y memoria
Carga datos de memoria a registros (LOAD) Almacena datos de
registros en Memoria (STORE) Transfiere el control del programa
Saltos incondicionales hacia/desde procedimientos Saltos
condicionales
Conjunto de instrucciones de la arquitectura Cuando se
diseñaron los primeros procesadores, una manera de
diseño consistía en agregar mas y mas instrucciones
a los CPU’s para elaborar tareas complicadas Por ejemplo,
la arquitectura de una VAX tiene instrucciones inclusive para
multiplicar polinomios Por otro lado, la filosofía de las
computadoras RISC (Cocke IBM, Patterson, Hennessy,
1980’s-Reduced Instruction Set Computing) que tienen un
Conjunto Reducido de Instrucciones para Cálculo tienen las
siguientes características: Mantiene el conjunto de
instrucciones pequeño y sencillo, de manera que se hace
fácil el diseño de hardware rápido Se le
deja al software realizar operaciones complicadas por medio de la
composición de instrucciones simples
Arquitectura del MIPS El procesador MIPS fue una de las primeras
arquitecturas que estaban disponibles comercialmente ¿Por
qué estudiar el MIPS en vez del procesador Intel x86? MIPS
es simple y elegante. No es necesario entrar con tanto detalle
con instrucciones que posiblemente ni siquiera se utilicen nunca
MIPS es ampliamente utilizado en dispositivos embebidos, los
x86son poco utilizados en sistemas embebidos, y existen
más computadoras embebidas
Registros (1/4) A diferencias de los lenguajes de alto nivel como
C o Java, el lenguaje ensamblador no utiliza variables
¿Por qué no? Esto hace que el hardware sea simple
Los operadores ensamblados son registros Hay un número
limitado de localidades especiales que son construidas
directamente en hardware Las operaciones sólo se pueden
realizar en estos registros Beneficios: Debido a que los
registros se encuentran directamente en hardware, son muy
rápidos
Registros (2/4) Desventajas: Debido a que los registros se
encuentran en hardware, hay un número predeterminado de
ellos Hay una solución: El código del MIPS debe ser
cuidadosamente programado para un uso eficiente de registros
Existen 32 registros en el MIPS ¿Por qué 32?
Mientras más pequeño más rápido A su
vez, cada registro del MIPS tiene un ancho de 32 bits Grupos de
32 bits es llamado palabra en el MIPS
Registros (3/4) Los registros son numerados del 0 al 31 Cada
registro puede ser referido por su número o por su nombre
Por ejemplo, referencias numéricas: $0, $1, $2, …,
$30, $31
Registros (4/4) Por conveniencia, cada registro tiene un nombre
para hacerlo fácil de codificar y recordar Por ahora para
nosotros: $16 A $ 23 equivalen a los registros $s0 a $s7 ( y que
corresponde a variables en C) $8 A $15 equivalen a $t0 a $t7 (que
corresponden a variables temporales) Posteriormente veremos los
nombres de los otros registros En general, se utilizan nombres
para que el código sea más entendible
Variables en C y Java vs. registros En C (y la mayoría de
los lenguajes de alto nivel) las variables se declaran al
principio y se les asigna un tipo Ejemplo: int fahr, celsius;
char a, b, c, d e; Cada variable puede SÓLO representar un
valor del tipo con el que fue declarado (no se puede mezclar y
empatar las variables int y char) En lenguaje ensamblador, los
registros no tienen tipo; el tipo de operación determina
como el contenido de los registros es tratado
Comentarios sobre lenguaje ensamblador Otra forma de hacer que el
código ensamblador sea más entendible es utilizar
comentarios El símbolo de gato (hash) # es utilizado por
MIPS para realizar comentarios Cualquier expresión
después de # hasta el fin de esa línea es un
comentarios y será ignorado Esto es igual que si se
utilizará en C99 el símbolo // Nota: Diferencias
respecto a C C también tiene el formato /* … */
para realizar comentarios De forma que se pueden incluir varias
líneas
Instrucciones en ensamblador En lenguaje ensamblador cualquier
expresión (llamada instrucción), ejecuta
exactamente un comando de una lista corta de éstos. A
diferencia de C (y la mayoría de los lenguajes de alto
nivel), cada línea de código ensamblado contiene al
menos 1 instrucción Las instrucciones están
relacionadas con las operaciones (=, +, -, ?, /) en C o en Java
Veamos ahora algunos ejemplos para MIPS
Suma y resta en el MIPS (1/4) Sintaxis de las instrucciones: 1 2,
3, 4 Donde: nombre de la operación operando donde se
obtiene el resultado (“destino”) 1er operando de la
operación (“fuente 1”) 2do operando de la
operación (fuente 2”) La sintaxis es rígida:
i.e. 1 operador, 3 operandos ¿Por qué? Mantiene al
hardware simple debido a la regularidad
Suma y resta en el MIPS (2/4) Suma en ensamblador Ejemplo: add
$s0, $s1, $s2 (en MIPS) Equivale a: a = b + c (en C) Donde los
registros del MIPS $s0, $s1 y $s2 están asociados a C con
las variables a, b y c respectivamente Resta en ensamblador
Ejemplo: sub $s3, $s4, $s5 (en MIPS) Equivale a: d = e – f
( en C) Donde los registros del MIPS $s3, $s4 y $s5 están
asociados a C con las variables d, e y f respectivamente
Suma y resta en el MIPS (3/4) ¿Cómo se implementa
la siguiente instrucción de C a MIPS? a = b +c + d –
e; La instrucción se divide en múltiples
instrucciones add $t0, $s1, $s2 # temp = b + c add $t0, $t0, $s3
# temp = temp + d add $s0, $st0, $s4 # a = temp – e Hay que
notar que una sóla línea en C puede dividirse en
varias instrucciones en MIPS Además, todo aquello
después del # en cada línea es ignorado
Suma y resta en el MIPS (4/4) ¿Cómo se implementa
la siguiente instrucción? f = (g + h) – (i + j); Se
utilizan registros intermedios add $t0, $s1, $s2 # temp = g + h
add $t1, $s3, $s4 # temp = i + j sub $s0, $t0, $t1 # f = (g + h)
– (i + j)
Registro cero Un valor inmediato particular, el número
cero (0), aparece en MIPS constantemente De manera que se ha
definido un registro cero ($0 o $zero) y que siempre tiene el
valor 0; e.g. add $s0, $s1, $zero (en MIPS) f = g (en C) Donde
los registros del MIPS $s0 y $s1 están asociados a C con
las variables f y g De acuerdo con la definición anterior,
la instrucción: add $zero, $zero, $0 No realizaría
nada
Valores inmediatos (1/3) Los valores inmediatos son constantes
numéricas Aparecen constantemente en el código para
los MIPS, de manera que son instrucciones especiales Suma
inmediata: addi $s0, $s1, 10 (en MIPS) f = g + 10; (en C) Donde
los registros del MIPS $s0 y $s1 están asociados a C con
las variables f y g La sintaxis es similar a la
instrucción add, excepto que el último argumento es
un número en vez de un registro
Valores inmediatos (2/3) No existe una instrucción de
resta inmediata en MIPS: ¿Por qué? Existe un
límite en el número de operaciones que pueden ser
implementadas físicamente con el propósito de
mantener un conjunto de instrucciones mínimo,
además se ahorra en área de silicio Si la
instrucción puede ser descompuesta e implementada por
otras operaciones que ya existen, entonces este tipo de
instrucción (posiblemente subi) no se incluye addi ., .,
-X = subi ., ., X ? de manera que no existe subi
Valores inmediatos (3/3) addi $s0, $s1, -10 (en MIPS) f = g
– 10; (en C) Donde los registros del MIPS $s0 y $s1
están asociados a C con las variables f y g
Trivia Los tipos están asociados con declaration en C
(normalmente), pero están asociados con instrucciones
(operadores) en MIPS Debido a que hay solamente 8 registros
locales ($s0-$s7) y 8 registros variables temporales ($t0-$t7),
no podemos escribir en MIPS expresiones de lenguaje C que
contengan más de 16 variables Si p (almacenado en $s0)
fuera un apuntador a un arreglo de enteros (ints), entonces p++;
pudiera ser implementado con la instrucción en MIPS: addi
$s0, $s0, 1
Resumen En el lenguaje ensamblador en MIPS: Los registros
reemplazan a las variables en C Se tiene una instrucción
(simple operación) por línea Simple es mejor
Más pequeño es más rápido
Instrucciones nuevas vistas: add, addi, sub Nuevos registros
Variables en C: $s0 – $s7 Variables temporales: $t0 – $t7 Cero:
$0 ó $zero
MIPS: lectura y almacenamiento, decisión
Ensamble de operandos: Memoria Las variables en lenguaje C se
mapean hacía registros; ¿Qué sucederá
con estructuras de datos grandes como los arreglos? En 1 de los 5
componentes principales de la computadora en este caso la
memoria, contiene tales estructuras de datos Sin embargo en el
caso de los MIPS las instrucciones aritméticas sólo
operan sobre registros, nunca directamente sobre la memoria Las
instrucciones de transferencia de datos se realiza entre
registros y memoria: De memoria a registros De registros a
memoria
Anatomía: los 5 componentes de cualquier computadora PC
Procesador Computadora Control (“cerebro”)
Pagtrón de datos Registros Memoria Despositivos Entrada
Salida (Gp:) Carga (desde) (Gp:) Almacenamiento
(hacía)
Los registros se encuentran en el camino de datos del procesador;
si los operandos se encuentran en memoria, se deben de transferir
los datos para que el procesador opere sobre ellos, y entonces se
vuelve a realizar la transferencia de nuevo a memoria A
continuación se presentan las instrucciones para
transferencia de datos
Transferencia de datos: memoria a registros (1/4) Para transferir
palabras a datos se necesita especificar dos cosas: Registros: se
especifican por medio de ($0-$31) o con un nombre
simbólico Dirección de memoria: es más
difícil de indicar Hay que pensar en la memoria como un
arreglo de una sóla dimensión, de manera que se
pueda direccionar de manera sencilla por medio de un apuntador a
una dirección de memoria En otras ocasiones, se debe de
ser capaz de realizar un desplazamiento (offset) a partir de este
apuntador Recordar: “leer (cargar) desde la
memoria”
Transferencia de datos: memoria a registros (2/4) Para
especificar a una dirección de memoria para copiar datos;
se especifican dos valores: Un registro que contenga un apuntador
a memoria Una desplazamiento (en bytes) La dirección de
memoria deseada es la suma de estos dos valores Ejemplo: 8($t0)
Especifica la dirección de memoria a la que se apunta a
partir de valor en $t0, más 8 bytes
Transferencia de datos: memoria a registros (3/4) Sintaxis para
la instrucción de lectura 1 2, 3(4) Donde Es el nombre de
la operación Registro que recibirá el valor
Corrimiento numérico en bytes Registro que contiene el
apuntador a memoria Nombre de la instrucción para el MIPS:
lw (significa “load word” (carga de palabra), de
manera que se cargan 32 bits o una palabra a la vez)
Transferencia de datos: memoria a registros (4/4) Ejemplo: lw
$t0, 12($s0) Esta instrucción tomará el apuntador
en $s0, se le sumarán los 12 bytes, y se cargará el
valor el valor de la localidad de memoria a la que se apunta
hacía el registro $t0 Notas: $s0 Es llamado registro base
El valor 12 será llamado corrimiento El corrimiento se
utiliza generalmente para accesar elementos del arreglo o de la
estructura: el registro base apunta al comienzo del arreglo o de
la estructura (hay que notar el corrimiento debe de ser una
constante conocida al momento de que se ensambla el programa)
Flujo de Datos
Transferencia de datos: registro a memoria (1/2) También
se desea almacenar desde un registro hacía una localidad
de memoria La sintaxis de la instrucción de escritura
(almacenamiento) es idéntica a la de lectura Nombre de la
instrucción para el MIPS: sw (que significa “store
word” (almacenar palabra), de manera que 32 bits o una
palabra se almacena a a la vez
Transferencia de datos: registro a memoria (2/2) Ejemplo: sw $t0,
12($s0) Esta instrucción tomará el apuntador en
$s0, le sumará 12 desplazamientos (bytes), y
almacenará el valor del registro $t0 hacía la
dirección de memoria Hay que recordar: “Almacenar
haciá la memoria” Flujo de Datos
Apuntadores vs. valores Concepto clave: un registro puede tener
cualquier valor de 32 bits. Ese valor puede ser (con signo) int,
un unsigned int, un apuntador (a dirección de memoria),
entre otros Si se escribe add $t2, $t1, $t0 entonces $t0 y $t1 lo
mejor será que tengan valores a sumar Si se escribe lw
$t2, 0($t0) entonces $t0 deberá tener un apuntador No hay
que confundir los conceptos
Direccionamiento: bytes vs. palabra Cada palabra en memoria tiene
una dirección, que es similar a un índice en un
arreglo: memoria[0], memoria[1], memoria[2], … Llamada la
dirección de la palabra Las computadoras necesitan un
acceso de 8 bits (bytes) así como a palabras (4 bytes por
palabra) Actualmente la dirección de memoria de las
máquinas como bytes (i.e., “direccionamiento por
bytes”) es de 32 bits (4 bytes) direcciones por palabra
diferida por 4 memoria[0], memoria[4], memoria[8], …
Compilación con memoria ¿Qué valor de
corrimiento en lw se debe de tener para seleccionar A[5] en C? El
corrimiento será 4?5 = 20 para seleccionar A[5]: byte vs.
Palabra Compilar a mano utilizando los registros: g = h + A[5];
g: $s1, h: $s2, $s3: Dirección base de A 1ra.
Transferencia de memoria al registro: lw $t0, 20($s3) # $t0
obtiene A[5] Suma 20 a $s3 para seleccionar A[5], y colocar en
$t0 A continuación añadirlo a h y colocarlo en g
add $s1, $s2, $t0 # $s1 = h + A[5]
Notas sobre la memoria Error: Al olvidar que una palabra
secuencial direcciona en máquina con un direccionamiento
de 1 byte no difiere de 1 Muchos programadores de lenguaje
ensamblador han trabajado sobre errores hechos bajo el supuesto
que la dirección de la siguiente palabra puede encontrarse
incrementando la dirección de un registro en 1 byte en vez
del tamaño de palabra en bytes Por lo tanto, hay que
recordar que tanto para lw y sw la suma de la dirección
base y el corrimiento debe de ser múltiplo de 4 (para que
quede alineado con la palabra)
Más notas sobre memoria: alineamiento MIPS requiere que
todas las palabras comiencen en un byte de dirección que
sea múltiplo de 4 bytes Llamado alineamiento: los datos
deben caer en una dirección que sea múltiplo de su
tamaño (Gp:) 0 1 2 3 (Gp:) Alineado (Gp:) No Alineado 0,
4, 8, or Chex Último dígito hex. de la dir. es: 1,
5, 9, or Dhex 2, 6, A, or Ehex 3, 7, B, or Fhex
ESTA PRESENTACIÓN CONTIENE MAS DIAPOSITIVAS DISPONIBLES EN
LA VERSIÓN DE DESCARGA