CC4301 Arquitectura de Computadores Luis Mateu - Johan Fabry [lmateu|jfabry]@dcc.uchile.cl Métodos de diseño de circuitos digitales Sistemas combinacionales En un circuito combinacional los valores de las salidas dependen únicamente de los valores que tienen las entradas en el presente. Se construyen a partir de compuertas lógicas: AND, OR, NOT u otras. x NOT AND y OR z NOT Ejemplo: Circuito C1 Para especificar la función de un circuito se usan: 1. Tablas de verdad: Para cada combinación de entradas se especifican las salidas. 2. Formulas Algebraicas: Se usa notación algebraica que se deriva directamente del circuito. x y z 0 0 1 0 1 1 z = ¬x . y + ¬y 1 0 1 Formula de C1 1 1 0 Tabla de verdad de C1 Observe que la tabla de verdad de C1 es idéntica a la tabla de NAND. Entonces C1 es equivalente a z = ¬(x.y) Dado que un NAND produce las mismas salidas que C1 y que un NAND necesita menos transistores que C1, entonces un NAND resulta más conveniente. Se necesita una metodología que permita transformar circuitos costosos en circuitos más simples. La teoría del Algebra de Boole nos permitirá comprender esta metodología. 1 Tabla de Compuertas Lógicas Símbolo x AND z y x OR Tabla de Verdad Significado z x 0 0 1 1 x 0 0 1 1 y-lógico o-lógico y x NOT x z negación z o-exclusivo NAND z y-negado NOR z o-negado XOR x 0 0 1 1 x 0 0 1 1 x 0 0 1 1 y x y x y 2 y 0 1 0 1 y 0 1 0 1 x 0 1 z 0 0 0 1 z 0 1 1 1 z 1 0 y 0 1 0 1 y 0 1 0 1 y 0 1 0 1 z 0 1 1 0 z 1 1 1 0 z 1 0 0 0 Formula Algebraica z = xy z=x+y z = ~x z=x!y z = ~(xy) z = ~(x+y) Algebra de Boole Un álgebra de Boole es una estructura algebraica denotada por < A , + , . , 0 , 1 >, en donde: • A es un conjunto • + y . son operadores binarios • {0, 1} ⊂ A La estructura verifica los siguientes axiomas: A1 asociatividad de + ∀ a, b, c : (a+b) + c = a + (b+c) = a+b+c A1' asociatividad de . ∀ a, b, c : (a.b) . c = a . (b.c) = a.b.c = abc A2 ∀ a, b: a+b = b+a conmutatividad de + A2' conmutatividad de . ∀ a, b: a.b = b.a A3 a+0 = a neutro para + A3' neutro para . a.1 = a A4 a . (b+c) = ab + ac distrib de . c/r a + A4' distrib de + c/r a . A5 a + (bc) = (a+b) . (a+c) existencia de complemento ∀a ∃!¬a tq a + ¬a = 1 y a . ¬a = 0 Teoremas básicos de un álgebra de Boole: T1 a+a=a T1' a.a=a T2 a+1=1 T2' a.0=0 T3 a + ab = a T3' a (a+b) = a T4 ¬¬a = a T5 ¬(ab) = ¬a + ¬b T5' ¬(a+b) = ¬a ¬b T6 a + ¬ab = a + b T6' a (¬a+b) = ab Otras maneras de escribir OR: + ∪ ∨ | || AND: . ∩ ∧ & && NOT: ā ʼ ¬ ~ ! 3 Propuesto: La estructura < {0, 1}, OR, AND, 0, 1> correspondiente a las compuertas lógicas es un álgebra de Boole, con ¬0 = 1 y ¬1 = 0 Demostración: usando tablas de verdad para verificar que se cumple cada uno de los axiomas. Ejemplo: (completar esta tabla) a b c 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1 ab (ab)c bc a(bc) 0 0 0 0 0 0 1 0 1 1 1 1 Nota: 3 variables implican 2^3 filas. Columnas 3 y 5 resultan ser iguales. Propuesto: Verifique la distributividad de + c/r . : a+(bc) = (a+b)(a+c) Demostración:! (ejercicio ...) Demostración de teoremas del álgebra de Boole: T1: a + a = (a + a) . 1 = (a + a) . (a + ~a) = a + (a . ~a) = a + 0 = a [A3ʼ, A5, A4ʼ, A5, A3] T1ʼ: Basta reemplazar en la demostración anterior los + por . , 0 por 1 , Ax por Axʼ , y viceversa. T4: Sea b = ~a , entonces: ! b + a = 1 ya que b + a = ~a + a = a + ~a = 1 [A2, A5] ! b . a = 0 ya que b . a = ~a . a = a . ~a = 0 [A2ʼ, A5] # luego a = ~b por la unicidad de A5 a = ~~a Propuesto: Mostrar que ~((~(a.~b)).~b) = a+b usando algebra de Boole Demostración:! (ejercicio ...) 4 Metodología de diseño de circuitos combinacionales a partir de una tabla de verdad. Por etapas: 1. Dada una tabla de verdad, obtener una formula algebraica. 2. Dada una formula algebraica obtener el diagrama circuital. Etapa 2 es la mas sencilla: Se construye el árbol de operaciones de la expresión y se reemplazan los operadores por las respectivas compuertas. x + . ~ y z ~y y ~y+z NOT x(~y+z)~z OR x + ~ . AND ~z ~(xyz) NOT NOT ~ z z x y f(x,y,z) OR xyz z AND y y ! Ejemplo: Circuito C2: f(x, y, z) = x(~y+z)~z+~(xyz)+y Etapa 1 es mas complejo. Por ejemplo considerando la tabla de verdad a la derecha: Por “simple inspección”: f(x,y) = ~xy + x~y por que ! f(x,y) vale 1 cuando x es 0 e y es 1 # f(x,y) vale 0 cuando x es 1 e y es 0 x y f(x,y) 0 0 0 0 1 1 1 0 1 1 1 0 Esta forma de determinar intuitivamente una formula está respaldada teóricamente por el siguiente teorema. Teorema de Shannon: Sea f:{0,1}n → {0,1} # f(x1 ... xi ...xn) =# xi . f(x1 ... 1 ... xn) + # # # # ~xi . f(x1 ... 0 ... xn) Demostración: Basta probar la igualdad para xi = 0 y xi = 1 Caso xi = 0 : f(x1 ... 0 ... xn) # # # # # # ! ! ! ! ! Caso xi = 1 : f(x1 ... 1 ... xn) # # # # # # # # # # # = 0 . f(x1 ... 1 ... xn)#+ ~0 f(x1 ... 0 ... xn) =0# # # + f(x1 ... 0 ... xn) = f(x1 ... 0 ... xn) = 1 . f(x1 ... 1 ... xn) #+ ~1 f(x1 ... 1 ... xn) = f(x1 ... 1 ... xn)# + 0 = f(x1 ... 1 ... xn) 5 Aplicando recursivamente el teorema: ! f(x1 ... xn) = # ~x1 . ... . ~xn-1 . ~xn .# f(0 ... 00) + # # # ~x1 . ... . ~xn-1 . xn . #f(0 ... 01) + # # # ~x1 . ... . xn-1 . ~xn . #f(0 ... 10) + # # # ~x1 . ... . xn-1 . xn . # f(0 ... 11) + # # # ... # # # x1 . ... . xn-1 . xn . # # f(1 ... 11) En el ejemplo: x y f(x,y) 0 0 0 0 1 1 1 0 1 1 1 0 f(x,y) # # # # ! # # # # =# # # # =# # # # =# ~x~y .# f(0,0) + ~xy .# f(0,1) + x~y .# f(1,0) + xy . # f(1,1) ~x~y .#0 + ~xy .# 1 + x~y .# 1 + xy . # 0 ~xy + x~y Observe que el teorema de Shannon es sólo válido para la lógica con {0,1}, no para cualquier álgebra de Boole. Metodo general 1. Dada la tabla de verdad para f, para cada fila se crea un sumando del tipo: ! [~]x1 [~]x2 ... [~]xn f(fila), en donde xi aparece negado si en esa fila xi = 0 2. Se eliminan todos los sumandos que se multiplican por 0 (porque x.0 = 0 y x . 1 = x) Ejemplo: g(x,y) = ~xy + x~y +xy x y g(x,y) 0 0 0 0 1 1 ~xy + 1 0 1 x~y+ 1 1 1 xy Pero claramente (!) g(x,y) = x + y Demostración: 1. Tabla de + igual a tabla de g 2. Usando álgebra de Boole: (xy = xy + xy) ~xy + xy + x~y = ~xy + xy + xy + x~y = (~x + x) y + x (y + ~y) = y + x = x+ y Con las siguientes dos reglas se puede llegar a simplificar cualquier fórmula del tipo suma de productos: 1. xy~z + x~y~z = x~z (no importa el número de variables) 2. x~yz + xyz + xy~z = xz + xy (porque = x~yz + xyz + xyz + xy~z) 6 Mapas de Karnaugh Para visualizar mejor los productos que se pueden reducir se usan tablas de verdad reordenadas, llamadas mapas de Karnaugh. Ejemplo: Mapa de Karnaugh: x y z f(x,y,z) 0 0 0 1 ~x~y~z + 0 0 1 1 ~x~yz + 0 1 0 0 0 1 1 1 ~xyz + 1 0 0 1 x~y~z 1 0 1 0 1 1 0 0 1 1 1 0 ! ! ! Fijense en el orden de yz ! x \ yz 00 01 11 10 0 1 1 1 0 ~x~y~z ~x~yz ~xyz 1 0 0 1 0 x~y~z f(x,y,z) = # ~y~z + # ~xz Principio fundamental: en una mapa de Karnaugh los términos que se originan de dos celdas adyacentes siempre difieren en una sola variable (Gray code). Los mapas son circulares: x \ yz 00 01 11 10 0 0 0 0 0 1 1 0 0 1 f(x,y,z) = x~z x~y~z Un 1 se puede usar más de una v e z e n u n a s i m p l i fi c a c i ó n , aprovechando que ~x~y~z = ~x~y~z + ~x~y~z f(x,y,z) = ~y~z + ~x~z xy~z x \ yz 00 01 11 10 0 1 0 0 1 ~x~y~z 1 1 x~y~z 7 ~xy~z 0 0 0 Se pueden agrupar bloques adyacentes: Agrupando 2n 1 se eliminan n variables. Cada 1 debe ser cubierto al menos una vez. f(x,y,z) = ~yz + yz = z Por 4 variables: x \ yz 00 01 11 10 0 0 1 1 0 ~xy~z ~xyz 1 1 xy~z xyz 1 0 0 xy \ zw 00 01 11 10 ! Fijense en el orden de xy ! 00 1 1 0 1 f(x,y,z,w) = # ~x~y~z + # # ~xyzw+ # # ~y~w 01 0 0 1 0 11 0 0 0 0 10 1 0 0 1 Observe que la función sin simplificar require 18 AND y 5 OR, mientras que al simplificarla queda en 6 AND y 2 OR. Mapas de Karnaugh de cualquier tamaño El problema se reduce a encontrar secuencias de los valores binarios en que los elementos adyacentes difieren en un solo bit (n-bit Gray codes). S1 = 0, # 1 S2 = 00,# 01,# 11,# 10, S3 = 000,# 001,# 011,# 010,# 110,# 111,# 101,# 100 ... informalmente: Sn+1 = 0.Sn + 1.invertir(Sn) 8 Problema: Construir un circuito que calcule la suma de 3 números binarios de 1 bit x,y,z, dando como resultado un número de 2 bits c, r (acarreo y resultado) . Tabla de verdad y mapas de Karnaugh: x y z c r 0 0 0 0 0 0 0 1 0 1 0 1 0 0 1 0 1 1 1 0 1 0 0 0 1 x \ yz 00 01 11 10 1 0 1 1 0 0 0 0 1 0 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 x \ yz 00 01 11 10 0 0 1 0 1 1 1 0 1 0 r = x~y~z + ~x~yz +xyz +~xy~z c = xy + yz +xz Diagrama circuital: 03_Sumador (1 of 1) X 0 Y 0 Z 0 0 R 0 C 9 Propuesto: usando álgebra de Boole, muestre que si se remplazan tanto AND como OR por NAND en este circuito, el circuito sigue calculando la misma función. Problema: Construir un sumador de 2 números binarios de 4 bits cada uno. Solución 1: usando la metodología recién vista. ! Con 8 entradas, la tabla de verdad tiene 256 filas ! Demasiado largo. c 0 0 1 0 x 1 1 0 1 y 1 0 0 1 1 0 1 1 0 Solución 2: usando el circuito sumador de 3 bits. Se suma cada columna con un sumador de 3 bits. Diagrama circuital: 04_Sumador4Bit (1 of 1) 0 X3 0 X2 0 Y3 0 X1 0 Y2 0 X0 0 Y1 0 Y0 0 0 0 Z3 0 Z2 0 Z1 0 Z0 Funciones incompletamente especificadas Es frecuente que el valor de una función ante ciertas entradas pueda ser 0 o 1. Eso se anota con X en la posición correspondiente de la mapa de Karnaugh: f(x,y,z,w) =# # # # # # # x+ z+ yw + ~y~w xy \ zw 00 01 11 10 00 1 0 1 1 01 0 1 1 1 11 X X X X 10 1 1 X X Las X se hacen valer 1 si esto es conveniente para obtener una fórmula más pequeña. 10 Sistemas Secuenciales Un circuito secuencial es un circuito en donde las salidas no sólo dependen de los valores de las entradas en el presente, sino que también dependen de un estado interno del circuito. Este estado interno varía en función de las historia de los valores de las entradas. Definición: Diagrama de tiempo es un gráfico de los valores que toman un conjunto de líneas (señales, conexiones) en función del tiempo. 1 x 0 x c = xy 1 AND y 0 1 c 0 r = x!y y XOR 1 r t 0 Ejemplo: Circuito C3 con su diagrama de tiempo En C3, los valores de c y r se calculan en función de los valores de x e y en el mismo instante. (En la práctica, el cambio en c o r siempre se produce con un pequeño retardo, después que cambian x o y, hoy es +- 10 picosegundos = 10-11 segundo) La flecha indica causalidad: el cambio de x origina el cambio en r. Al contrario, no hay causalidad entre el cambio de x e y. En un diagrama de tiempo, no es necesario colocar todas las flechas de causalidad, sólo se colocan aquellas que sean útiles para el lector del diagrama. Resumiendo: en el ejemplo de circuito combinacional C3 ! # ! # c(t) = f(x(t),y(t)) r(t) = g(x(t),y(t)) Definición: Reloj (ϕ) es una compuerta que genera una señal que cambia periódicamente de valor. Pulso de bajada Pulso de subida T ! t Valores típicos de período del reloj T = 125 microsegundos a 0.3 nanosegundos. frecuencia = f = 1/T = 8Mhz a 3.3 Ghz 11 Circuitos Secuenciales En este tipo de circuitos, las salidas no sólo dependen de las entradas, sino que también dependen del estado interno del circuito. Concretamente: 1. Un circuito secuencial posee un número finito de estados. En un instante dado el circuito se encuentra en uno y solo uno de sus estados. 2. Una de las entradas del circuito secuencial se denomina el reloj. El intervalo entre dos pulsos de bajada del reloj es un ciclo del circuito. El estado del circuito se mantiene constante durante un ciclo del reloj. 3. En un instante dado, el circuito calcula sus salidas en función de sus entradas, exceptuando el reloj, y de su estado interno. 4. Al final de cada ciclo (justo antes del pulso de bajada del reloj) el circuito calcula su estado válido para el próximo ciclo en función de su estado actual y sus entradas. Un circuito secuencial actua como circuito combinacional dentro de un ciclo. Si cambian sus entradas, tambien pueden cambiar sus salidas. Especificación de circuitos secuenciales: Diagrama de estados Un diagrama de estados especifica formalmente el comportamiento de un circuito secuencial. 0*/0 11/1 A C 00/0 11/1 *0/0 xy/z 01/0 10/0 1*/1 01/1 B Ejemplo: Diagrama de Estados E1 para un circuito con entradas x e y y salida z. Los círculos rotulados representan los estados del circuito. Las flechas representan las transiciones de estado y los rótulos especifican las salidas en función de las entradas para el estado del cual sale la flecha. El comportamiento informal de un circuito secuencial se aprecia mejor en un diagrama de tiempo. x y z ! t C B 12 A Un diagrama de estados especifica completamente un circuito secuencial. Por lo tanto por cada estado salen flechas con rótulos para todas las combinaciones de entradas posibles. El * en una entrada se usa para especificar 2 rótulos simultáneamente: uno con esa entrada en 0 y el otro en 1. Al contrario de un diagrama de estado, un diagrama de tiempo es una especificación incompleta del circuito. Pueden haber muchos diagramas de estado que satisfacen el mismo diagrama de tiempo. Pero dado un diagrama de estado, un estado inicial y valores para sus entradas durante un intervalo de tiempo, el diagrama de tiempo para sus salidas es uno y sólo uno. Sin embargo, un problema usual es encontrar al menos un diagrama de estado que satisfaga un diagrama de tiempo. Por ejemplo, el siguiente diagrama de tiempo describe informalmente un circuito detector de secuencias 1 1 en su entrada. x y ! t Observe que el circuito secuencial solo puede registrar lo que sucede al final de cada ciclo. El circuito puede señalar que hubo dos 1 consecutivos aún cuando x tuvo una corta transición a 0 en la mitad del ciclo. Un diagrama de estado para este diagrama de tiempo es: 1/0 0/0 A B 0/0 1/0 0/1 C 1/1 Los rótulos subrayados son necesarios para que este diagrama de estado satisfaga el diagrama de tiempo anterior. 13 El circuito secuencial mínimo: el Flip-Flop Data d d Data q q ! ! t 1/0 0/0 A B 1/1 0/1 En un ciclo, el valor de q es el valor que tenia d justo antes del comienzo del ciclo (i.e. el pulso de bajada del reloj). El flip-flop data es el primero de los biestables que veremos. Es importante porque permite almacenar un valor binario durante todo un ciclo. Si se desea almacenar varios bits basta colocar varios flip-flop en paralelo. Los microprocesadores usan flip-flops data para almacenar los registros. qn d q Data dn ! n+1 ... n+1 Reg ! q0 d q Data d0 ! ! Implementación de circuitos secuenciales El problema esencial es representar y almacenar el estado interno del circuito. Supongamos que sabemos implementar el flip-flop data. Entonces podemos codificar los estados de cualquier circuito secuencial en la forma de números binarios y luego utilizar flip-flops data para almacenar la codificación elegida. 14 Forma general de la implementación de un circuito secuencial: Este circuito secuencial puede poseer a lo más 2 n estados posibles. En cada ciclo el circuito combinatorio recibe las entradas del circuito secuencial: (X) y la codificación del estado actual (Q), los que usa para calcular las salidas (Y) y la codificación del próximo estado (D). El registro almacena D en el pulso de bajada del reloj y se lo envía al circuito combinatorio (Q), lo que marca el comienzo de un nuevo ciclo. X Entradas Y Salidas Circuito Comb. Q D n n Reg Q D ! Metodología de diseño de un circuito secuencial, a partir de un diagrama de estado Presentaremos esta metodología aplicando al diagrama de estados E1 Primero: Codificación de estados: para representar 3 estados, se necesitan log2 3 flip-flops data: Estado q1 q0 A 0 0 B 0 1 C 1 0 Segundo: Tablas de verdad: a partir del diagrama del estado se construye una tabla de verdad para especificar las salidas Z, d1 y d0 del circuito combinacional. q1 q0 x y d1 d0 z q1 q0 x y d1 d0 z A 0 0 0 0 1 0 0 C 1 0 0 0 0 0 0 A 0 0 0 1 1 0 0 C 1 0 0 1 0 1 1 A 0 0 1 0 0 1 0 C 1 0 1 0 0 1 1 A 0 0 1 1 1 0 1 C 1 0 1 1 0 1 1 B 0 1 0 0 0 0 0 ? 1 1 0 0 X X X B 0 1 0 1 1 0 0 ? 1 1 0 1 X X X B 0 1 1 0 0 0 0 ? 1 1 1 0 X X X B 0 1 1 1 0 0 1 ? 1 1 1 1 X X X 15 Tercero: Mapas de Karnaugh y fórmulas algebraicas q10 \ xy 00 01 11 10 q10 \ xy 00 01 11 10 00 1 1 1 0 00 0 0 0 1 01 0 1 0 0 01 0 0 0 0 11 X X X X 11 X X X X 10 0 0 0 0 10 0 1 1 1 d1 = ~q1~q0~x + ~q1~q0y +~q1~xy d0 = q1y +~q0x~y q10 \ xy 00 01 11 10 00 0 0 1 0 01 0 0 1 0 11 X X X X 10 0 1 1 1 z = xy + q1y + q1x Cuarto: Diagrama circuital completo (ejercicio): basta hacer el circuito para el circuito combinacional y insertarlo en el circuito secuencial. 16 Implementación de Biestables Previo: Tiempos de Retardo Hasta el momento se ha supuesto que las salidas de una compuerta elemental (ej: AND) se calculan en forma instantánea. En un diagrama de tiempo, esto se graficaría: x x(t) z(t) = x(t).y(t) AND y y(t) z En la realidad una compuerta calcula sus salidas con un cierto tiempo de retardo ε: x x(t) z(t) = x(t-!).y(t-!) AND y y(t) z ! No todas las compuertas tienen el mismo retardo, pero por simplicidad supondremos el mismo tiempo de retardo para todas las compuertas básicas. Para n compuertas básicas anidadas (puestas en cascada) el tiempo de retardo es T = n.ε Para un sumador de números de n bits que usa el circuito sumador de 3 bits en cascada (i.e. la generalización del sumador de 4 bits visto en la fin de circuitos combinacionales). Si en un instante dado el bit menos significativo de un argumento cambia, después de cuanto tiempo se ha calculado la nueva suma? Solución: Si cada sumador de 3 bits tiene un retardo 3ε, T = 3εn 17 El Biestable Reset/Set ~R ~R ~Q ~S Q ~S ~Q Q Descripción: Cuando ~R = ~S = 1, Q y ~Q se mantienen constantes y con valores opuestos: ! Si ~R → 0 Q → 0, ~Q → 1 1 ~R ! Si ~S → 0 Q → 1, ~Q → 0 ! ¡ ~S = ~R = 0 esta prohibido ! (Por que ?) 0 ~Q Q Observacion: R viene de Reset, y S de set. ~S La negación sobre R y S (~R y ~S) significa 0 que la acción Reset o Set se produce cuando la señal está en 0 (y no en 1). Existe un biestable que usa R y S, pero históricamente ha sido más caro de fabricar, (usa NOR en vez de NAND) entonces no se usa. 1 El Latch WR L ~Q L WR Q Q L ~Q 0 ~R ~S Descripción: Mientras WR = 1, Q = L. Cuando WR = 0, Q vale lo que tenía L justo antes del pulso de bajada de WR. 0 WR 18 1 0 Q El Flip-Flop Data D ~Q l D ~Q 1 Q 0 Latch 1 Latch Q D WR Latch Q D ~Q WR WR = 1 en Pulso de subida Latch Q ~Q WR = 1 en Pulso de bajada La implementación visto es de un registro master/slave: equivalente a un flip-flop data pero mas robusto, porque el flip-flop data depende de los tiempos de retardo:. Ejercicio: Verifique en un diagrama de tiempo que el Flip Flop Data sólo cambia en el pulso de bajada de ϕ . Para ello fije valores para D y ϕ y en los valores iniciales de l y Q. Luego calcula L, Q y ~ϕ en función de D y ϕ. Observación: El latch no sirve para hacer circuitos sequenciales. Ejemplo: en el siguiente circuito secuencial si se reemplaza el Flip Flop Data por un latch: Cuando ϕ está en 1, Q adquiere valores oscilatorios que difícilmente pueden servir para algo. D Q ! Q D L WR Q WR Q L 19 Diseño Modular de Circuitos El número de filas de la tabla de verdad de un circuito combinacional aumenta exponencialmente con el número de entradas (mientras que el número de columnas aumenta linealmente con las entradas y salidas). Esto significa que la metodología que vimos no sirve para circuitos que tienen muchas entradas. Los circuitos complejos (con muchas entradas) se diseñan acoplando circuitos más simples. Esto último se denomina diseño modular. Estrategias de Diseño Modular x0 x1 Primero: Diseño en Paralelo: Ejemplo a la derecha. Las salidas de 1 no dependen de ninguno de los cálculos que se hacen en 2, y viceversa 1 es completamente independiente z0 z1 y0 y1 de 2. Segundo: Diseño en Cascada: Por ejemplo el sumador de números de n bits que usa el circuito sumador de 3 bits en cascada. Una de las entradas de cada sumador de 3 bits (el carry) depende de una salida de otro sumador de 3 bits. x0y0 x1y1 1 x0 x1 Tercero: Diseño Serial o Secuencial. El cálculo se descompone en n etapas o cálculos intermedios. Cada uno de estos cálculos se ejecuta en un ciclo del reloj. La idea es utilizar el mismo módulo para realizar los n cálculos intermedios en n ciclos. Ejemplo: un sumador serial. z0 AND y0 2 y1 z1 AND Cuarto: Diseño en Pipeline: Esto se verá al final del curso. Estrategias de comunicación entre módulos Primero: Punto a Punto# # 1 Segundo: 1 a n# 2 1 2 # # Tercero: n a 1 1 MUX 3 2 Cuarto: Bus 1 2 S 3 20 3 Elementos modulares para el diseño de circuitos Decodificador ... Descripción: si EN = 0 todos los Qj están en 0. si EN = 1 y el valor de A2-A0 es i, entonces Qi = 1 y el resto = 0. EN ... Decod 3x8 Q7 A2 Q6 A1 A0 Q1 Q0 Qj = { 0 si EN = 0 0 si i ! j, EN = 1 1 si i = j, EN = 1 Implementación: 1 0 0 1 0 1 ... 0 21 Descodificador de 6x64 Se usan 9 decodificadores 3x8 puertas en cascada y/o paralelo. EN A5 A4 A3 EN A2 A1 A0 Decod 3x8 Q7 Q0 Decod 3x8 Q1 ... Q1 Q0 EN Q7 ... Q8 Decod 3x8 Cascada Q9 ... EN Q15 Paralelo ... Com punto a punto Com 1an Q56 Decod 3x8 Q57 ... EN 22 Q63 Multiplexor: x3 x2 x1 x0 z=xi z MUX x3 x2 x1 x0 A0 A1 EN 1 i Implementación usando compuertas tristate: # y = x si c = 1, y = tristate si c= 0 x y x A0 A1 x3 x2 x1 x0 y C Decod 2x4 C Una salida en estado tristate está desconectada y por lo tanto no hay corto circuito si otra fuente aplica un potencial a la misma línea. EN 1 Decod 2x4 A0 A1 1 0 1 1 0 0 0 1 1 1 0 1 1 1 0 0 0 0 !corto! tristate Sumador Serial El sumador recibe serialmente dos números binarios de menor a mayor significancia. La suma comienza cuando H pasa de 0 a 1 y termina cuando ! H pasa de 1 a 0. H x y H SUM ! r x y r XXXXXXXX XXX 0 c 23 0 1 1 H H ! ! y x Diseño modular en serie: se reutiliza el sumador de 3 bits ya visto y se introduce un circuito sequencial que se acuerda del acarreo c. c out SUM 3 bits Control c in c r H Cin/Cout 0*/X 10/0 11/1 11/0 Diagrama de estados para el circuito “Control” (estados = carry generado en el ciclo anterior). c=1 c=0 10/1 0*/X Left Shifter / Right Shifter X << m X0 Yj = { << 1 Xn-1 Yn-1 Xn-2 Yn-2 Xn-1 Yn-1 Xn-2 Yn-2 X Y=x<<m >> m X0 Y0 0 si j < m Yj = Xj-m si j >= m { Y=x>>m Xn-1 Xn-2 Yn-1 Yn-2 Y0 0 X0 Y0 0 si j >= n-m Xj-m si j < n-m Solo mostramos la implementación de <<1 Nótense que m es una constante fija para el circuito. Más tarde usaremos estos circuitos para implementar un circuito que reciba m como argumento. Conditional Left Shifter / Right Shifter n n X X Y << m? n n X Y X Y >> m? X C C C Y= { X si c = 0 X << m si c = 1 Y= { Y X Y X si c = 0 X >> m si c = 1 Solo mostramos la implementación de >>m? 24 >> m A=1 A=0 MUX A Y Barrel Shifter Implementación: usando las siguientes propiedades: m = K0.20 + K1.21 + K2.22 + K3.23 + K4.24 + K5.25 # y! x<<(n+l) = (x << n) << l x << m = (((((x << K5.25) << K4.24) << K3.23) << K2.22) << K1.21) << K0.20 Usando esta última fórmula haremos una implementación en cascada. << 32? X C << 16? << 1? ... C Y C K5 K4 ... K0 La Multiplicación en Cascada X Y 0101 * 1010 0000 Si es 0, la fila es 0 0101 0000 Si es 1, la fila es 1 + 0101 Z 0110010 X 4 4 Y Ext 4!8 8 .c Y0 X.Y0 La multiplicación en binario es más simple que la misma en decimal. El resultado del producto de 2 números de n bits tiene 2n bits como máximo. << 1 Y1 (X<<1) .Y1 .c + << 1 Y2 Para la implementación necesitamos un circuito que extiende un número de n bits a uno de m bits, agregando 0 a la izquierda, y un multiplicador por un valor de un bit (es decir, calcula x. 1 o x.0). La implementación de ambos es trivial (Primero: inputs siempre 0. Segundo: usando compuertas AND). .c (X<<2) .Y2 + << 1 Y3 (X<<3) .Y3 .c + 8 25 Z El problema de este circuito es que es my costoso. Para multiplicar cantidades de 32 bits se requieren 31 sumadores ! Por esta razón, los microprocesadores más económicos realizan una multiplicación en serie. Pseudo-code: Mult (x,y de 32 bits){ sea Rx de 64 bits = Ext 32→64 (x); sea Ry de 32 bits = y; sea Rz de 64 bits = 0; while (Ry != 0) { if(Ry[0] != 0) Rz += Rx; Rx := Rx << 1; Ry := Ry >> 1; } return Rz; } La multiplicación en serie se implementará en un circuito con el siguiente diagrama de tiempo. ! H x XXXXXXXX a XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX y XXXXXXXX b XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX rdy z XXXXXXXXXXXXXXXXXXXXXXXXXX a*b La transición de 0 a 1 en H indica que en x e y hay valores que deben multiplicarse. El multiplicador calcula de a un bit por cada ciclo. 26 Implementación: 32 Y X 32 >> 1 A=0 rdy Ry MUX A=1 A Ry[0] Ext 32!64 << 1 A A=1 MUX A=0 Rx .c + .c H Rz Control ! 64 27 Z Arquitectura Lógica Es la visión que tiene un programador de la máquina, sin importar su implementación. Se preocupa de: • La representación de los números y datos • Las operaciones realizables • En lenguaje de máquina o conjunto de instrucciones. Representacón de números La máquina representará los números en palabras de 8, 16, 32 o 64 bits. Una palabra es una secuencia de bits y sirve para almacenar enteros sin signo, enteros con signo, caracteres, números reales, etc. Usaremos la siguiente notación para una palabra x de n bits : x = |xn-1 xn-2 ... x1 x0| El valor numérico almacenado en la palabra x depende de la representación usada: Primero: Enteros sin signo# [[x]]u = i=0∑n-1 xi2i Observe que [[ ]]u es una función que recibe una secuencia de bits (una palabra) y entrega un numero ∈ [0,2n-1] Segundo: Enteros con signo# # # # # # [[x]]s #= [[x]]u si x = |0 xn-2 ... x1 x0| # = [[x]]u-2n si x = |1 xn-2 ... x1 x0| Con [[ ]]s : palabra de n bits → [-2n-1, 2n-1-1[ En el siguiente gráfico se observa la diferencia entre [[x]]u y [[x]]s para n = 3 [[000]]s = 0 , [[011]]s = 2(3-1)-1 = 3, [[100]]s = -(2(3-1))-(23) = -4 , [[111]]s = -1 7 Unsigned 6 Signed 5 4 3 2 1 0 -1 -2 -3 -4 000 001 010 011 100 28 101 110 111 Tercero: Números reales de precisión simple (norma IEEE) ! ! [[ | s | exp1 ... exp7 | frac0 ... frac22 | ]]f ! ! ! = 1.frac.2(exp-127).1 si s = 0 ! ! ! = 1.frac.2(exp-127).-1 si s = 1 ! ! ! Además exp ~= 0 y exp ~= 127 Cuarto: Números reales de precisión doble (norma IEEE) ! ! [[ | s | exp1 ... exp10 | frac0 ... frac51 | ]]f ! ! ! = 1.frac.2(exp-1023).1 si s = 0 ! ! ! = 1.frac.2(exp-1023).-1 si s = 1 ! ! ! Además exp ~= 0 y exp ~= 1023 No todas las arquitecturas ofrecen números reales por hardware. Ej: M32 (que veremos más tarde) no lo ofrece. Operaciones entre enteros: Suma de enteros Sea x, y palabras de n bits. sumador(x,y) = {x ⊕ y, Carry(x,y)} ⊕ es la salida del sumador visto en clases truncado a n bits y corresponde a la suma de palabras. En general se cumple que el número representado por la suma de las palabras x e y es la suma de los números representados por x e y. Es decir: ! ! ! ! [[x ⊕ y]]u =?= [[x]]u+ [[y]]u 0 1 0 1 →u 5 0 1 0 1 →u 5 ⊕ 0 0 1 1 →u + 3 ⊕ 1 1 1 1 →u + 15 1 0 0 0 →u ✓ 8 [1] 0 1 0 0 →u ✗✗ 4 Pero dado que el resultado de ⊕ se trunca a n bits, la suma no coincide cuando hay desborde. Observe que 5 + 15 = 20 no es representable en 4 bits. Veamos qué ocurre cuando vemos las misma suma pero usando enteros con signo: 0 1 0 1 →s 5 0 1 0 1 →s 5 ⊕ 0 0 1 1 →s + 3 ⊕ 1 1 1 1 →s + -1 1 0 0 0 →s ✗✗ -4 0 1 0 0 →s ✓ 4 5+3 = 8 no es representable en 4 bits con signo. La idea fundamental es que ⊕ coincide con la suma si el resultado de la suma es representable en n bits. 29 Matemáticamente escribimos: [[x ⊕ y]]u =mod2n= [[x]]u + [[y]]u y [[x ⊕ y]]s =mod2n= [[x]]s + [[y]]s con a =modm= b a modm = b modm. Conclusión fantástica: el sumador visto en clases sirve para sumar números sin signo y números con signo. De ahí el interés de la representación usada para los enteros con signo. Resta de Enteros x - y = x ⊕ ~y ⊕ 1 Definición:! ! Propuesta: [[x - y]]u =mod2n= [[x]]u - [[y]]u y [[x - y]]s =mod2n= [[x]]s - [[y]]s - 1 1 0 1 →u 13 0 1 0 1 →u - 5 1 0 0 0 →u ✓ 8 - 1 1 0 1 →s -3 0 1 0 1 →s - 5 1 0 0 0 →s ✓ -8 1 ⊕ 1 1 0 1 1 0 1 0 1 0 0 0 Conclusión más fantástica: el sumador visto en clases nos sirve también para restar números, con signo o sin signo. En realidad, la representación de los números con signo se escogió cuidadosamente de manera que el mismo sumador sirviera para realizar todas las operaciones. Esto se debe a que en los primeros computadores colocar circuitos distintos para realizar estas 4 operaciones (2 sumas y 2 restas) era demasiado caro. 30 Conversion entre palabras de distinto tamaño Reducción: se eliminan los bits más significativos. # Trunc n→m ( | xn-1 ... xm-1 ... x0 | ) = | xm-1 ... x0 | # Propuesta: [[Trunc n→m(x)]]u =mod2m= [[x]]u [[Trunc n→m(x)]]s =mod2m= [[x]]s Si una palabra de n bits contiene un número representable en m bits, la operación de truncación no altera el número representado. 0 0 1 0 1 →u 5 0 ↓ Trunc 5→4 1 1 0 1 →u 5 1 0 1 0 →u 26 1 ↓ Trunc 5→4 0 1 0 1 0 1 →s 5 ↓ Trunc 5→4 0 1 0 0 1 0 1 →s 5 1 0 1 0 →s -6 ↓ Trunc 5→4 →u 10 1 0 1 0 →s -6 1 0 1 →s -3 1 →s 13 (porque 26 no es representable en 4 bits, en cambio, -6 si) Extensión sin signo: Se agregan 0s a la izquierda Definición: Ext um→n ( | xm-1 ... x0 | ) = | 01 ... 0n-m xm-1 ... x0 | Propuesta: [[Extum→n(x)]]u = [[x]]u 1 1 0 1 →u 13 1 ↓ Extu4→5 0 1 1 0 ↓ Extu4→5 1 →u 13 0 1 1 0 Observe que al extender una palabra con ceros su valor sin signo se preserva. Sin embargo su valor con signo no necesariamente se mantiene, a pesar que el número -3 es representable en 5 bits. 31 Extensión con signo: se repite el bit de signo. Definición: Ext sm→n ( | xm-1 ... x0 | ) = | xm-1 ... xm-1 xm-1 ... x0 | # # (n-m veces xm-1) Propuesta: [[Extsm→n(x)]]s = [[x]]s 1 1 0 1 →u 13 1 ↓ Exts4→5 1 1 1 0 1 0 1 →s -3 1 →s -3 ↓ Exts4→5 1 →u 20 1 1 1 0 Conclusión: La truncación es la misma para números con signo y sin signo, pero la extensión es distinta. Esto no es grave, porque sigue siendo trivial hacer un circuito que extienda con o sin signo de acuerda a lo que diga una de sus entradas. 32 Intel x86 Assembler x86 no es una CPU, sino que es una arquitectura. • Primera CPU de 16 bits: Intel 8086, 1978. Con un conjunto de instrucciones IA-16. • Primera CPU de 32 bits: Intel 80386, 1985. Con un conjunto de instrucciones IA-32, compatible con IA-16. • Primera CPU de 64 bits: AMD Athlon 64, 2003. Con un conjunto de instrucciones AMD64, no compatible con Intel IA-64, x86-64 y IA-64, pero compatible con IA-32. ! → Nosotros nos centraremos en mostrar el conjunto de instrucciones IA-32 Propiedades Básicas de la Arquitectura: • CISC (Complex Instruction Set Computer), instrucciones de largo variable • Little-Endian • 8 registros para ʻuso generalʼ, 8 registros de punto flotante • Pila Registros Registro: memoria de la CPU. Casi todas las operaciones son llevadas a cabo usando registros como fuente de información o como destino. ! ! {A, B, C, D} 8 bit 8 bit !H !L 8 bit 8 bit 8 bit 8 bit 8 bit SI !X ESI = stream source E!X 8 bit 8 bit 8 bit 8 bit 8 bit 8 bit 8 bit 8 bit 8 bit DI EFLAGS Overflow, Carry, Sign, Zero 8 bit Direction, Aux Carry, Interrupt, Parity, 8 bit 8 bit EDI = stream dest 8 bit 8 bit 8 bit ... 8 bit BP EBP = stack base pointer 8 bit 8 bit 8 bit 8 bit 8 bit SP EIP = instruction pointer ESP = stack pointer No hablamos de los registros segment, kernel, application special purpose, punto flotante. 33 Preliminaries Los diferentes ensambladores existentes en el mercado usan diferentes sintaxis para escribir programas. En este apunte nosotros usaremos la sintaxis de GNU Assembler (GAS) = AT&T sintaxis para escribir (otro ʻstandardʼ : sintaxis Intel). Para ver un programa de ejemplo: gcc -S -O helloworld.c El tamaño de la memoria que se use para operar debe ser especificado poniendo como sufijo de la instrucción, sino no puede ser determinada automáticamente Sufijos: b = byte = 8 bit, w = word = 16 bit, l = long = 32 bit Instrucciones Aritméticas Una instrucción contiene los siguiente argumentos: • dest (destino): Puede ser un registro o una ubicación en memoria. • src (fuente): Puede ser un registro, una ubicación la memoria o una constante. • Si dest es un argumento entonces el resultado esta contenido en dest. • Uno de los dos argumentos debe ser un registro (add, sub, adc, sbb). adc src, dest sbb src, dest Add/subtract contents of mul src Unsigned/signed multiply src by src to/from dest. imul src value in corresponding register (see below). Add, setting carry bit Subtract using carry bit. inc dest dec dest Faster add 1, dest Faster sub 1, dest neg dest = dest * -1 add src, dest sub src, dest div src Unsigned/signed divide value in idiv src corresponding register by src (see below). If quot does not fit, arithmetic overflow interrupt. mul arg / imul arg div arg / div arg op size arg 2 dest high dest low op size dividend remain quot 8 bits AL AH AL 8 bits AX AH AL 16 bits AX DX AX 16 bits DX,AX DX AX 32 bits EAX EDX EAX 32 bits EDX,EAX EDX EAX Ejemplo (# indica un comentario) inc %eax! ! ! incl %eax!! ! add $42, %bh! ! addb $0x2A, %bh! sbbw %ax, %bx! ! # # # # # add ‘1’ to the value in the register eax idem add ‘42’ to the value in the register bh idem subtract ax from bx, result is in bx Los operadores $42 y $0x2A se llaman immediate operands.(operadores inmediatos) 34 Addressing modes, MOV and LEA La instruccion mov es usada para mov src, dest copiar información desde la memoria a un registro, desde un registro a memoria o desde la memoria a otra ubicación de la memoria. Copy data from src to dest La forma más fácil de usar la instrucción mov es copiando operados dentro de registros o copiar valores desde un registro a otro de manera inmediata. Ejemplo: movl $42, %eax movb %ah, %bl Para mover información entre la memoria y los registros, dos maneras de direccionamiento son usadas por la memoria operando: displacement addressing mode y register indirect addressing mode. La manera básica de direccionar memoria es llamada direccionamiento directo (direct addresing mode) o solo desplazamiento (displacement addressing mode). La dirección sería un desplazamiento desde la base de la memoria, 0. mov copia información desde una ubicación de la memoria a un registro o desde un registro a una ubicación en la memoria. Example code: .data! ! foo:!! ! ! .int 2! ! ! ! #seccion data, para declarar varibles globales #variable llamada ‘foo’ #es un entero con valor ’2’ [...] .text! ! ! .global _main !! _main: [...] #seccion de texto, el codigo va aqui #una funcion global llamada ’_main’ ! movl $6, foo! ! ! ! ! [...] #copia ’6’ a la ubicacion #de la memoria de ‘foo’ 35 El direccionamiento indirecto (register indirect addressing mode) permite direccionar indirectamente una ubicación de la memoria a través de un registro. El operando no es la dirección actual sino que el es valor especifico de la dirección a usar. Esto es similar a usar punteros en C. Para calcular la dirección serán necesarios 4 parámetros. Por lo tanto la dirección vendría dada por: disp + base + index * scale disp(base, index, scale) keyword meaning examples dereferencing what disp displacement foo(,1) foo pointer (not in a register) base base register (%eax) -4(%ebp) address in eax address 4 bytes above stack base index index register foo(,%eax) foo array index eax, if foo contains 1byte sized data scale constant 1, 2, foo(,%eax,4) foo array index eax, if foo contains 44 or 8 byte sized data 0(%eax,%ebx,2) index ebx of array starting in eax, if contains 2-byte sized data Ejemplo: Dado int i, *a, r; calcula r = (-a[i]) * 10; Suponiendo i in ebp - 4, a in ebp - 8, r in ebp - 12 movl -4(%ebp), %eax ! ! movl -8(%ebp), %ebx!! ! movl 0(%ebx,%eax,4), %eax! negl %eax!! ! ! ! imull $10, %eax! ! ! movl!%eax,-12(%ebp)!! ! #get i #get a #get a[i] #-a[i] #(-a[i]) *10, truncate to 32 bits #copy to r Todo direccionamiento a la memoria se puede hacer así, también cuando se esta haciendo una operación aritmética. Es útil por el ejemplo si queremos tener un loop: inc -4(%ebp) ! ! #i++ Para cargar dentro de un registro una dirección de memoria hay que usar load effective address . Ejemplo: lea 0(%ebx,%eax,4), %eax!! lea foo, %eax! ! ! ! lea mem, reg Load the address of the memory location mem into reg #copy address of a[i] (above) to eax #copy address of foo in eax Para aumentar el tamaño de una variable (Ej: de byte a int), se usan las operaciones move and extend. Estas operaciones reciben dos sufijos: tamaño de la fuente y tamaño de destino. Ejemplo: movb $-204, %al movsbw %al, %bx movs src, dest Extend considering sign movz src, dest Extend with 0ʼs 36 Pruebas y Saltos Para ejecutar un salto (Ej: para hacer un if ) hay que llevar a cabo dos pasos: 1. Hacer una operación de prueba, que da valor a EFLAGS 2. Saltar según sea el contenido de EFLAGS. cmp arg1, arg2 Like sub arg1, arg2 test arg1, arg2 Bitwise AND La ejecución de los tests no produce ningún resultado, solo da un valor al registro EFLAGS. Los saltos van a un lugar especificado en el código (Ver ejemplos). je loc jne loc Jump on equal Jump on not equal jnz loc jz loc Jump on not zero Jump on zero jg loc jge loc Jump on greater Jump on greater or equal ja jae jg for signed numbers jge for signed numbers jl loc jle Jump on lesser Jump on lesser or equal jb jbe jl for signed numbers jle for signed numbers jo loc Jump on overflow jmp Jump always Ejemplos: cmp %ax, %bx jg label1!! test $1, %ebx jnz sig_u:! label1: test %ah, %ah jz label3!! sig_u: cmp %al, %ah jz l_42! ! # salta a label1 si ax > bx # salta a sig_u si bit menos signific de ebx es 1 # salta a label3 si ah = 0 (ah && ah = ah) # salta l_42 si ah = al Ejemplo: dado int *a, n, s = 0, i = 0 escriba while (i<n) s+= a[i++] Asuma *a en eax, n en ebx, s en ecx, i en esi movl $0, %ecx! ! ! ! movl $0, %esi! ! ! ! L1: cmp %ebx, %esi!! ! ! jge L2 movl (%eax, %esi, 4), %edx! addl %edx, %ecx! ! ! addl $1, %esi! ! ! ! jmp L1 L2: [...] #s #i #i ? n #a[i] #s += a[i] #i++ 37 Operaciones Lógicas, Shift y Rotate and src, dest or src, dest xor src, dest Bitwise and Bitwise or Bitwise xor shr dest shl dest Shift right 1 bit, pad with 0 Shift left 1 bit, pad with 0 not dest Bitwise inversion sar dest sal dest Shift right 1 bit, pad with sign bit Shift left 1 bit, pad with 0 ror dest rol dest rotate right 1 bit rotate left 1 bit scr arg scl arg shr with bit shifted into carry flag shl with bit shifted into carry flag Rotates son como shifts, salvo que el bit que se pierda es usado como relleno. Manipulación del Stack La CPU mantiene una pila en la memoria donde el tope de la pila esta referenciado por el registro esp. Esta pila crece hacia abajo en la memoria, es decir, después de hacer push el tope de la pila será una ubicación menor. Push y pop toman un immediato, registro o dirección de memoria como argumento. Tamaño minimo de un push (and pop) es 16 bits push src Push value on stack, decrease esp pop dest Pop stack to dest, increase esp Para eliminar datos desde el tope de la pila, sin tener que usar pop: addl $x, %esp (donde x es el tamaño en bytes del dato). Para copiar datos desde el tope de la pila sin usar mov: x(%esp), dest (donde x es el offset del tope de la pila). Para copiar datos dentro de la pila: mov src, x(%esp) Subroutine Calls El CPU también tiene un registro (escondido), call lab push IP then jmp lab que se llama Instruction Pointer (IP). El IP apunta al lugar en memoria donde se encuentra ret pop into IP la instrucción que se esta ejecutando. Manipular ese registro permite saltos y subrutinas. El stack se usa de la siguiente manera para implementar llamadas a subrutinas. 1. El llamador apila argumentos en la pila en orden inverso y llama con call (lo que apila el Instruction Pointer) 2. Se encadenan los registros de activación ebp 3. Se resguardan registros que no deben ser modificados según la “convención del llamador” 4. Se agranda el registro de activación para variables locales 5. Se calcule el valor 6. El valor retornado queda en eax 7. Se botan los variables globales y se restituyen los registros guardados 8. El llamado retorna usando ret (lo que depila el Instruction Pointer) 9. El llamador depila los argumentos 38 Ejemplo: Llamador Llamado res = proc(arg1, arg2) int proc(int p1, int p2){ int local; [...] return local; } Convención de llamador: el llamado debe preservar ebx, esi, edi, esp, ebp, y puede modificar eax, ecx, edx. pushl arg2 pushl arg1 call proc movl %eax, res addl $8, %esp # # # # # paso paso paso paso paso 1 1 1 6 9 .global proc proc: pushl %ebp # paso 2 movl %esp, %ebp # paso 2 pushl %edi # paso 3 pushl %esi # paso 3 pushl %ebx # paso 3 subl $4, %esp # paso 4 [...] # paso 5 movl -16(%ebp), %eax # paso 6 addl $4, %esp # paso 7 popl %ebx # paso 7 popl %esi # paso 7 popl %edi # paso 7 leave # paso 7 ret # paso 8 #leave = #movl %ebp, %esp #popl %ebp 39 Buffer overflow attacks La manera en que x86 hace llamadas a subrutinas permite fácilmente que ret no salta de vuelta a la instrucción original pero a otro lugar en memoria. Eso se logra sobreescribiendo ebp + 4 Cuando se hace ret cualquier dato en memoria a ese lugar se considera como la próxima instrucción y se ejecutara. Escribir en ebp + 4 puede ser accidental, causando un crash, pero también puede ser intencional, en un caso de ataque por buffer overflow. Esto ultimo se puede hacer abusando la próxima función: int getUserID(){ ! char BUF[7]; ! [ get user id as string, ! put in BUF ] ! return [ convert BUF]; } .global getUserID getUserID: ! pushl %ebp! #p2 ! movl %esp, %ebp! ! subl $8, %esp! ! [ get user input ] [ convert to int, put in ! addl $12, %esp!! ! ret! ! ! ! #p2 #p4 eax ] #p7 #p8 Supngamos que el usuario entra “12345678....****”. Eso se escribe en BUF, cual se ubica en el stack. Stack Contiene Debería contener ESP + 12 “****” = 0x2A2A2A2A ebp + 4 = IP del llamador (jmp) ESP + 8 “....” = 0x2E2E2E2E ebp del llamador (paso 2) ESP + 4 “5678” BUF[4] ! BUF[6],\0 “1234” BUF[0] ! BUF[3] ESP (El char * tienen ascii code 42 = 0x2A, y . tiene ascii code 56 = 0x2E) Resultado: con el ret el CPU salta a la dirección de memoria 0x2A2A2A2A y ejecuta cualquier código que se encuentra alla ! 40 Formato de instrucción prefix opcode mod-reg-r/m sib 1 or 2 bytes 0 ! 4 bytes displac immediate 1 byte 1 byte 0,1,2 or 4 bytes 0,1,2 or 4 bytes mod reg r/m scale index base 2 bits 3 bits 3 bits 2 bits 3 bits 3 bits register operand register value other operand register Ejemplos: addl # # # %ebx # # # %esp # # # addl # # # -8(%ebp), # # # # # # ! # # # = | 0000 0011 1110 0011 | = | opcode | mod-reg-r/m | = | 0000 0011 | 11 | 100 | 011 | = | addl | reg | esp | ebx | %esi# = | 0000 0011 0111 0101 1111 10000 | # = | opcode | mod-reg-r/m | immediate | # = | 0000 0011 | 01 | 110 | 101 | 1111 1000 | # = | addl | esi p1 | ebp | esi p2 | -8 | El código de operación (opcode) puede ser distinto para la misma instrucción: Instrucción opcode motivo addl -8(%ebp), %esi 0x03 “Normal” addl %esi, -8(%ebp) 0x01 Invertido addb %dh, -8(%ebp) 0x00 Bytes + Invertido addb $1, %esi 0x83 Immediato Falta addw %si, -8(%ebp) . Por eso se necesita usar un prefijo. El prefijo 0x66 indica que los operados son de 16 bits. Entonces: addw %si, -8(%ebp)! = | 0x66 | 0x01 | ... 41 SPARC Assembler SPARC = Scalable Processor Architecture • Primero CPU: SPARC, 32 bits, 1987-1992 • Primero CPU 64 bits: UltraSPARC, 1995 • Arquitectura abierta y sin derechos exclusivos. ! → Nosotros nos centraremos en SPARC Architectural basic properties • RISC (Reduced Instruction Set Computer) • Big-Endian (hasta UltraSparc: Bi-Endian) • 32 registros ʻde uso generalʼ visibles, 32 registros de punto flotante • Usa ventana de registros RISC: Diseño de U.California Berkeley • Set de instrucciones minimalista, fácil a descodificar • Instrucciones ortogonales • Meta: 1 instrucción cada ciclo del reloj • Branch Delay Slot • 2 implementaciones ʻdirectasʼ : SPARC y MIPS Registros r0 ... r7 = g0 ... g7 Globales f0 ... f31 punto flotante r8 .. r15 = o0 ... o7 Output pc program counter r16 ... r23 = l0 ... l7 Locales psr processor state register r24 ... r31 = i0 ... i7 Input y usado por mul y div El registro g0 siempre contiene un cero y no puede ser modificado. Por convenciones de software, algunos registros tienen usos especiales: i6, i7, o6, o7, g1 ... g7. Estos registros no se pueden usar. El programador debe usar los registros locales primero, luego i0 ... i5, luego o2 ... o5. g1 ... g4 volátiles (usado por el compilador) i6 = fp frame pointer (stack pointer del llamador), debe preservarse o6 stack pointer o7,i7 dirección de retorno 42 Llamadas y Ventanas de Registros Hay 2 → 32 conjuntos de 16 registros, en un momento dado, se puede ver 1 conjunto, llamada ventana de registros. Los registros in, local y out dependen de la ventana, los global son independientes. Para cambiar de ventana se usa SAVE (próxima ventana) y RESTORE (ventana anterior). o0 ... o7 son los registros i0 ... i7 de la próxima ventana: i0...i7, l0...l7, SAVE RESTORE o0...o7 i0...i7, l0...l7, o0...o7 i0...i7, l0...l7, o0...o7 Window overflow: un SAVE agota los registros. El Sistema Operativo debe resguardar registros en memoria para liberar registros. Instrucciones Formatos de instrucción simple y uniforme: 32 bits (const = 13-bit signed int, address = 22-bit signed int, ! = commentario) • Operaciones aritméticas / lógicas con operandos en registros opcode %reg1, %reg2, %reg3 ! reg3 = reg1 op reg2 opcode %reg1, const, %reg2 ! reg2 = reg1 op const • Lectura de registros en memoria LDx [%reg+const], %reg LDx [%reg+%reg], %reg LDx [%reg], %reg ! [%reg] = [%reg+%g0] • Escritura de registros en memoria STx %reg, [%reg+const] STx %reg, [%reg+%reg] • Instrucciones de control: saltos y subrutinas Bx address • Cargar direcciones y NOP SETHI %hi(const32),%reg ! carga 22 bits de orden superior OR %reg,%lo(const32),%reg ! los 10 restantes SET const32,%reg ! op sintética del assembler NOP ! = SETHI 0,%G0 Más info en el fichero ʻsparc.pdfʼ (de http://www.cs.umanitoba.ca/~cs222/sparc.ps) 43 Ejecución de instrucciones Conceptualmente: 1. 2. 3. 4. Fetch: instr = (%pc) %pc = %pc + 4 Examinar código de operación (primeros 8 bits) de instr Ejecutar Nota: 1 y 2 se puede ejecutar en paralelo de 3 y 4 para mejor rendimiento. Branches y Branch Delay Slot Por lo anterior, en SPARC, la instrucción siguiente al branch también se ejecuta ! Lo usual es poner ahí alguna instrucción cuya ejecución también sea útil, o bien un NOP. Lo que no se puede colocar en ese "delay slot" es otra instrucción branch, ni tampoco una instrucción SET (porque esta última es, en realidad, dos instrucciones). 44 Formato de instrucción Load/store: op [rs1 + rs2], rd (load) o op rd,[rs 1+ rs2] (store) (asi = address space indicator) 31 30 29 11 25 24 19 18 rd op 14 13 12 rs1 0 5 4 asi 0 rs2 op [rs + siconst12 ], rd (load) o op rd, [rs + siconst13] (store) 31 30 29 11 25 24 19 18 rd op 14 13 12 rs1 0 1 siconst12 Operaciones aritméticas / lógicas op rs1, rs2, rd 31 30 29 10 25 24 19 18 rd op 14 13 12 rs1 0 5 0 4 0 rs2 op rs1, siconst13, rd 31 30 29 10 25 24 19 18 rd op 14 13 12 rs1 0 1 siconst12 SetHI: 31 30 29 00 25 24 rd 22 21 0 100 const22 Conditional branches 31 30 29 28 00 a 25 24 cond 22 21 0 010 disp22 Call 31 30 29 01 0 const30 Descodificacion: Primary opcode y Secondary opcode (Inspirado por http://www.cs.unm.edu/~maccabe/classes/341/labman/node9.html ) 45 Arquitectura física de un Computador Consideramos el computador con la arquitectura M32 (m32-arch.pdf) I/O IOC IOC A D C CPU Memoria A D C A D C C L K direcciones datos control Toda la comunicación entre estos módulos se realiza a través de los buses. Hay 3 buses: 1. Bus de Datos : D31 - D0 2. Bus de Direcciones : A31 - A2, BE3 - BE0 3. Bus de Control: indica el tipo de operación a realizar RD = lectura, WR = escritura, CLK = el reloj, WAIT = sirve para prolongar el acceso a la memoria. La CPU o procesador es el interprete del lenguaje de máquina. Todo el proceso de una instrucción se realiza internamente en la CPU. En la CPU estan los registros (R0 - R31), el contador de programa (PC) y registro de estado (SR). Los programas y los datos se almacenan en memoria. Examinemos en un diagrama de tiempo la ejecución de la instrucción stw R2, [R3+8]. ! RD WR A XXXXX XX PC D XXX R3 + 8 X R2 XX inst 1 2 XXX 3 4 5 Esta instrucción se c o d i fi c a e n u n a palabra de 32 bits en memoria. Para ejecutar una instrucción, el procesador pasa por 3 fases: I. Fetch: En esta fase se carga la instrucción que está en la memoria en un registro interno de la CPU. La fase fetch se ejecuta en 2 ciclos del reloj. (1) La CPU coloca el PC en el bus de direcciones. (2) La CPU activa RD y mantiene el PC en el bus de direcciones. La memoria responde a la señal RD colocando el contenido de la dirección especificada. II. Decodificación: En esta fase el procesador examina la instrucción y decide como ejecutarla. Para esto se necesita solo el ciclo (3) III. Ejecución: En esta fase se ejecuta la instrucción. Esta fase depende del código de operación de la instrucción. Como la instrucción es un stw la CPU la ejecuta en 2 ciclos: (4) La CPU calcula la dirección como R3+8 y la coloca en el bus de direcciones.(5) la CPU mantiene la dirección en el bus y activa la señal WR. Al mismo tiempo coloca R2 en el bus de datos. La memoria modifica el contenido de la dirección especificada con el dato que aparece en el bus. 46 La Memoria Existen varios tipos de memoria: SRAM, DRAM, SDRAM, ROM, EPROM, Flash ... SRAM = Static Random Access Memory Memoria estática de acceso aleatorio. Son rápidas, se borran si se apagan, necesitan 4 o 6 transistores por bit. Se etiquetan como [numero de palabras] x [ancho de palabra] y [tiempo de acceso]. RD, CS Dirección Read Write Chip Select A17-A0 256K x 8 RD 12 ms WR D7-D0 CS A Datos X XX D Tiempo de accesso Comportamiento en un diagrama de tiempo: El tiempo de acceso es el tiempo transcurrido desde que se coloca una dirección en A hasta que la memoria entrega el dato. Implementación: Este tipo de chip se puede implementar usando latches para cada bit. RD L Din Latch WR Se usa un descodificador para seleccionar la palabra que se va a escribir o leer. Q Dout WR N columnas WR = 0 ... Bit M WR Decod M+1 x M+1 2 CS EN A WR = 1 ... 2M filas ... ... RD DN-2 DN-1 47 D0 Para conectar este chip a la CPU, hay que resolver varios problemas: Primero : Tamaño de palabra del bus de datos (32) y el ancho de memoria (8) no coinciden. Por lo tanto hay que colocar varios chips en paralelo: (todo los chips se activan al mismo tiempo). (A1 y A0 siempre son 0 por motivo de alineamiento de memoria.) A31 -A2 A17-A0 RD WR 256K x8 CS D7-D0 D31-D24 256K x8 D23-D16 256K x8 D15-D8 256K x8 256K x 32 WR RD D7-D0 Capacidad total: 1 MB. Problema: Las direcciones 0, 1MB, 2 MB, ... son sinónimos. Si se escribe x en la dirección 0 , también se leerá x en las direcciones 1MB, 2 MB, ... Segundo : La memoria solo debe accesarse en el rango de direcciones [0, 1MB[. Pero: a ∈ [0, 1MB [ a = |a31 a30 ... a20 a21 ...a2 a1 a0| = |0 0 ... 0 ? ? ... ? 0 0| SEL [0,1MB[ A31-A20 CS RD WR (1MB = 2^20) Para limitar el rango de acceso, usamos la linea CS (Chip Select) de la memoria. Un chip se accesa solo si CS está en 1. Si éste está en 0, el chip no participa en un accesso, CS se calcula como Ahora, si se lee la dirección 1MB se lee basura, puesto que CS permanece en 0: La memoria no entrega nada en el bus de datos. Te r c e r o : L a m e m o r i a p u e d e s e r insuficiente. Se coloca un módulo de 1MB en las direcciones [0, 1MB[ y otro modulo en [1MB, 2MB [ WR RD Observe que estos módulos nunca se activan al mismo tiempo. SEL [0,1MB[ = 1 # Si (RD+WR) y A ∈ [0, 1MB[ # i.e. a # = |a31 a30 ... a20 a21 ... a2 a1 a0| # # = |0 0 ... 0 ? ? ... ?0 0| SEL[1MB, 2MB[ = 1 # Si (RD+WR) y A ∈ [1 MB, 2MB[ # i.e. a # = |a31 a30 ... a20 a21 ... a2 a1 a0| # # = |0 0 ... 1 ? ? ... ? 0 0| A31-A2 A31-A20 SEL [0,1MB[ A17-A0 RD WR 256K x 32 CS D31-D0 48 Ejercicio: Interfaz de memoria de 8MB, ubicada en [0, 8MB[ Solución: Se colocan 8 bancos de 1MB y se activan por medio de un descodificador. a ∈ [0, 8MB [ a = |a31 a30 ... a23 a22 a21 a20 a21 ... a2 a1 a0| = |0 0 ... 0 b2 b1 b0 ? ? ... ? 0 0| Cuarto : La memoria puede tener un tiempo de acceso muy extenso hay que agregar por ejemplo 2 ciclos de espera (wait states). Cuando la CPU está en el segundo ciclo de una lectura o escritura y detecta que la línea WAIT está en 1 en el pulso de bajada del reloj, repite el segundo ciclo hasta que WAIT se ponga en 0 Para activar la línea WAIT se coloca un circuito generador de ciclos de espera. Implementación: clase auxiliar. DRAM = Dynamic RAM Son los más usadas debido a su baja costo por bit. Se implementan con un condensador para almacenar 1 bit y un transistor para seleccionarlo: SEL Para escribir el bit se coloca SEL en 1 y un 0 o 1 en DATA, lo que DATA descarga o carga el condensador. Cuando SEL=0 el condensador se mantiene aislado, por lo que mantiene su carga por algunos milisegundos. Para leer el dato se coloca SEL en 1. Si el condensador está cargado, se descarga generando una débil corriente que es detectada como 1. Esto significa que la lectura es destructiva: después de leer un bit, hay que reescribirlo para no perder los 1. Las desventajas de este tipo de memoria son: 1. lento: tiempo de acceso de 60 -70 ms (vs 10 - 20 ms para las estáticas) 2. cada 4 milisegundos hay que reescribirlos completamente. Esto se debe a que los condensadores se descargan lentamente, aún cuando no se lean. Las DRAM se venden en chips o DIMMs (Dual In-line Memory Module), sucesor al SIMM (Single In-line Memory Module). SIMMs de 72 pins tienen un bus de datos de 32 bits o 36 si incluyen paridad. La paridad es un bit adicional redundante que se calcula como el XOR de 8 bits de datos. La paridad permite detectar errores de almacenamiento en la memoria dinámica, producidos ej. por la descarga de un condensador. Durante la lectura se verifica que la paridad almacenada coincide con la paridad calculada a partir de 8 bits de datos. Si no coincide se genera un interrupción por “parity error”. La paridad siempre detecta errores de 49 un solo bit en un byte. Error-Correcting Memory usa un sistema de paridad (Hamming Codes) para corregir errores: en la reescritura de memoria se detecta errores y los datos corregidos son reescrito en memoria. Articulo: “DRAM Errors in the Wild: A Large-Scale Field Study” Bianca Schroeder, U of Toronto, y Eduardo Pinheiro y Wolf-Dietrich Weber, Google. (SIGMETRICS/ Performanceʼ09) Resumen: Estudio de 2.5 años de DRAM de “the majority of machines in Googleʼs fleet”. • “About a third of all machines in the fleet experience at least one memory error per year”. • “In more than 93% of the cases a machine that sees a correctable error experiences at least one more correctable error in the same year.” • “20% of the machines with errors make up more than 90% of all observed errors” • DIMM error rates are [...] a mean of 3,751 correctable errors per DIMM per year. Dirección multiplexada 11 Row Access Column Access A21-A11 /A10-A0 RAS CAS RD WR Din Dout Los chips que vienen en un DIMM se organizan como una matriz de bits. El acceso a la memoria dinámica es más complejo que el de la memoria estática debido a la multiplexión del bus de direcciones. A21-A11 11 ... Latch CAS A D Decod 11x2K RAS RAS XX fila XX colum X bit 2K 1 bit ... 11 2K CAS RD 11 Latch A10-A0 Amplific, latch multiplex Din RD WR Dout Durante el acceso a la fila, se lee toda la fila de condensadores. Como la lectura es destructiva (es decir descarga los condensadores), la fila se reescribe completamente a final del acceso. En el acceso a la columna se selecciona un bit dentro de la fila. Por otra parte, cada fila debe ser reescrita cada 4 milisegundos para que no se borre. Esta se logra con un acceso a la fila sin acceso a la columna. Des esto se encarga el hardware en forma transparente para el software. En general, un chip no entrega o recibe datos bit por bit, pero usa bloques de 8 bits (un byte). También por cada acceso a una fila se pueden accesar 4 columnas dentro de esa 50 misma fila, lo que permite un acceso secuencial más eficiente que el acceso aleatorio. Este tipo de acceso se denomina “page mode”. SDRAM = Synchronous DRAM + DDR = Double Data Rate La interfaz a SDRAM es sincrónico: DRAM entregue los datos ʻlos mas rápido posibleʼ, SDRAM espera hasta x ciclos del reloj (= CAS latency, especificado por software). Así funciona de manera sincrónico con el CPU. El uso del bus de datos es más complicado: por una escritura los datos tienen que estar en el bus de datos en el empiezo de la operación, en una lectura, los datos aparecen en el bus de datos en la fin de la operación (n ciclos del reloj más tarde). El circuito que maneja el bus tiene que asegurar que no hay colisiones. Una evolución de page mode es ʻburst modeʼ. En burst mode, se lee o escribe automáticamente los datos de las 4 columnas dentro de una fila en 4 ciclos sucesivos del reloj. Se resulta muy útil para vaciar o llenar el caché del CPU (ver mas tarde). Double Data Rate SDRAM acelera burst mode acesando no solo datos en el pulso de bajada de la reloj, pero también en el pulso de subida. El bus de datos ahora funciona a doble velocidad: double data rate. Sinónimos por double data rate son, double pumped, dual-pumped, y double transition. Ojo: la transmisión de datos dentro de un burst es 2 veces más rápido, pero el tiempo de acceso de memoria no cambia. ROM = Read Only Memory Vienen grabados de fabrica y no se pueden escribir. No se borran al apagarlas, son lentas. Se usan para almacenar el programa de bootstrap (el cargador del sistema operativo) o en control automático/ embarcado. Son relativamente económicas pues requieren de a lo más 1 transistor por bit. SEL SEL 0 1 PROM = Programmable ROM Es una ROM que se programa con dispositivos especiales. Cada bit se implementa con un transistor y un fusible. Los 0 se pueden cambiar por 1 aplicando una intensidad fuerte de corriente para quemar el fusible. Pero una vez que se quema el fusible no se puede volver a 0. SEL DATA Luego de grabarlas, se instalan en el computador, el que no puede modificarlas. La ventaja sobre el ROM es que no es necesario pedirle al fabricante que las grabe. 51 EPROM = Erasable PROM Son similares al las PROM, pero el fusible se puede restituir con luz ultravioleta aplicada en una ventana de cuarzo ubicada sobre el silicio. La luz solar contiene luz ultravioleta de modo que una vez que se borra su contenido se debe tapar la ventana, para que no se borre. Flash Este es un tipo de memoria que no se borra al apagarla, pero que se puede escribir en el mismo computador, aunque no tan eficientemente come una DRAM o SRAM. Se puede cambiar 0 por 1 fácilmente, pero se borran por filas completas. Tiene un limite a cuantas veces puede ser escrito y borrado (write-erase cycles). 52 La CPU Consideramos la CPU M32 (m32-imp.pdf m32-modules.pdf) El el circuito se puede distinguir: • Unidades de almanacamiento: PC, R-BANK, IR, SR, AR • Unidades combinacionales: ALU, MUX, ... • Unidad de control • Buses internos: ➡ • Señales de control ⎯> que van de la unidad de control a las componentes. ALU (Arithmetic/Logic Unit) Realiza todos los cálculos del tipo z = x op-alu y en donde op-alu es la operación que indique la unidad de control. Además indica las características del resultado en ZVSC Control Unit Circuito secuencial muy complejo que genera las señales de control. Estas hacen fluir los datos y direcciones por los buses que interconectan las unidades de almacenamiento y las de cálculo. R-Bank (Register bank) Almacena los registros R0-R31 En s1 y s2 se indica el numero de los registros que aparecen constantemente por RS1 y RS2. Además si WR-Rd = 1 se escribe en Rd en forma síncrona en d (la actualización se produce en el pulso de bajada) Registros síncronos: PC y SR Mantienen el contador de programa y el registro de estado ZVSC Registros asíncronos: IR y AR Mantienen la instrucción en curso y la dirección que se debe colocar en el bus de direcciones. R-SEL Extrae de la instrucción los números de los registros que intervienen en una operación Y-SEL Elige el segundo operando de una instrucción, el que puede ser 0, 4 o lo que diga la instrucción (RS2 o imm) o un desplazamiento de 24 bits en caso de un salto Branch Unit Calcula la condición de salto durante instrucción del tipo b<cond> <label> DBI (Data Bus Interface) Interfaz con el bus de datos. Durante lecturas y escrituras, la memoria espere bytes y halfwords en posiciones distintas de donde se encuentra en la palabra original. DBI desplaza los datos y extiende el signo si es necesario. ABI (Address Bus Interface) Genera los valores de A31-A2 y BE3-BE0 a partir de una dirección y el tipo de operación que le indique la unidad de control. 53 Funcionamiento En cada ciclo del reloj la unidad de control genera las señales de control para llevar los datos de los registros a los buses externos y a las unidades de calculo. Restricciones • Las señales de control permanecen constantes durante todo un ciclo del reloj. Solo cambian en el pulso de bajada del reloj. • En un bus se puede colocar un y solo un dato en un ciclo del reloj • La actualización del PC, SR, y Rd ocurre solo en el pulso de bajada del reloj. • Cada unidad de cálculo puede realizar un y solo un cálculo en cada ciclo del reloj. Etapas en la ejecución de una instrucción del tipo add R3, R4, R11 1 instrucción ! RD A XXXXX XX PC D XX inst fetch1 fetch2 decode exec Para describir las operaciones que se llevan a cabo en cada ciclo se utilizan dos niveles de arbitracción.! Transferencia entre registros Señales de Control fetch1 : ! AR := PC ! goto fetch2 OP-Y-SEL := @0 OP-ALU := @OR WR-AR, EN-A OP-ABI := @W fetch2: ! IR := Memw[AR] ! if WAIT goto fetch2 ! else goto decode OP-DBI := @LDW SEL-D, WR-IR, EN-A, RD OP-ABI := @W decode: ! PC := PC ⊕ 4 ! goto execute1 OP-Y-SEL := @4 OP-ALU := @ADD WR-PC Importante: en un mismo ciclo el orden en que se indican las transferencias o señales de control es irrelevante pues todas ocurren al mismo tiempo (en paralelo).Las señales de control no especificadas permanecen en 0. 54 Las transferencias entre registros están restringidas por lo que permiten hacer las componentes, buses y señales de control. Supongamos que la instrucción es add R4, -103, R11 Transferencia entre registros Señales de Control exec1 : ! R11 := R4 ⊕ -103 ! goto fetch1 SEL-REG, WR-RD, WR-SR, RD-DEST OP-Y-SEL := @INST OP-ALU := @ADD Supongamos que la instrucción es stb R3,[R5 + R0] Transferencia entre registros Señales de Control exec1 : ! AR := R5 ⊕ R0 ! goto exec2 SEL-REG, WR-AR, EN-A OP-Y-SEL := @INST OP-ALU := @ADD OP-ABI := @W exec2 : ! Memb[AR] := Truncb[R3] ! if WAIT goto exec2 ! else goto fetch1 SEL-REG, RD-DEST, EN-D,EN-A, WR OP-Y-SEL := @0 OP-ALU := @OR OP-DBI := @STB OP-ABI := @B Supongamos que la instrucción es bg <label> Transferencia entre registros Señales de Control exec1 : ! if br? goto exec2 ! else goto fetch1 exec2 : ! PC := PC ⊕ Exts(IR[23-0])) ! goto fetch1 WR-PC OP-Y-SEL := @DISP OP-ALU := @ADD 55 Implementación de la ALU x y OP-ALU Cin 4 MUX Y/~Y Y X Y AND X Y XOR Y Y SRA X Y OR 000 X X 001 C ADD Y SLL X C X SRL 110 MUX 3 SEL z31 VCS Z z En cada ciclo del reloj la ALU realiza en paralelo las 7 operaciones que implementa. Un multiplexor se encarga de seleccionar la operación que indique la unidad de control. La unidad ADD calcula V y C ! C = ultimo carry ! V = 1 si x31 = y31 != z31 , V = 0 sino El resto de la unidades V = 0, C = Cin Ejercicio: completar la tabla de verdad para el circuito combinacional: OP-ALU Cin C SEL Y/~Y (ADD) 0000 x 0 110 1 x⊕y (ADDX) 0001 0 0 110 1 x⊕y⊕c 1 1 110 1 x⊕y⊕c x 1 110 0 x⊕~y⊕1 (SUB) 0010 56 Calcula El banco de registros 1 registro sincrono: 1 WR S 0 MUX F/F Data ! 31 registros + R0 d s1 Rd s2 Dec 5x32 Reg 1 Reg 2 Reg 31 0001 0010 1111 0 0000 MUX 0000 0001 0010 1111 MUX Rs1 Rs2 57 ! La unidad de control */ 00 (OP-Y-SEL) */ 0101 (OP-ALU) */ 1 (WR-AR) */ 1 (EN-A) */ 00 (OP-ABI) */ 0 (*) Corresponde a un circuito secuencial complejo. AR := PC Fetch 1 (WAIT) 1 / % (WAIT) 0 / 000 (OP-DBI) (WAIT) 0 / 1 (SEL-D) ... Fetch 2 Decode OP = ADD / ... OP = JMPL / ... OP = ADDX / ... ... OP = LDW / ... Dado que M32 no tiene más de 32 instrucciones sólo se necesitan 5 bits para c o d i fi c a r t o d a s l a s instrucciones. IR := Memw[AR] Exec2 PC := PC ! 4 ... OP = LDW / ... Exec1 ... OP = JMPL / ... En un ciclo ocurren las siguientes acciones: 1. La unidad de control calcula las señales de control para dirigir los datos hacia las unidades de cálculo. 2. Las unidades de cálculo efectúan sus cálculos 3. Los resultados son almacenados en algún registro Cada una de estas acciones toma un tiempo de retardo. El periodo del reloj debe ser fijado de modo que se alcancen a realizar en un ciclo del reloj. La evolución de los microprocesadores está dirigida por lograr mayor rapidez al mismo precio. Para lograr mayor rapidez se lucha por: 1. Hacer que los transistores reaccionen más rápido disminuye el periodo del reloj. 2. Mejorar la tasa de instrucciones ejecutadas por ciclo de reloj (M32: 1 por cada 4 o 5 ciclos). 3. Disminuir el tiempo de calculo de señales de control y tiempo de calculo dentro del procesador Una forma de lograr esto último es en vez de calcular las señales de control en este ciclo, calcularla en el ciclo precedente. Dicho de otra forma: en cada ciclo se calculan las señales de control que se usarán en el próximo ciclo. Estas señales se dirigen a un registro sincrono que se actualiza en el pulso de bajada del reloj. Notense que esta optimisación no sería posible aplicarla si no existia el ciclo decode, pues la instrucción que fijará las señales de control que se aplicarán en el ciclo exec. 58 Entrada / Salida Boton Problema: Cómo comanda/examina la CPU a los dispositivos de entrada y salida? Bus E/S Indicador Entrada / Salida mapeada en memoria Los dispositivos se disfrazan de memoria, reaccionan en un rango de direcciones. Para examinar un dispositivo Un programa lee en una dirección del dispositivo. Para comandar un dispositivo Ejemplo: para saber si el botón se fue presionado se lee en la dirección 0xf000 Para encender o apagar el botón se escribe en la dirección 0xf000. Un programa escribe en una dirección del dispositivo A15-A0 D7-D0 SEL A=0xf000 D0 D0 RD WR WR Latch 5V Amplificador Supongamos que R1 = 0xf000 ldw [R1 + 0 ], R2 ! R2 = |?...?b|, b = 0 botón presionado, b = 1 botón arriba. ! (en C: R = *((char*) 0xf000) ) stw R3, [R1 + 0] ! R3 = |?...?1| apaga la luz ! ! R3 = |?...?0| enciende la luz (en C:*((char*) 0xf000) = R ) Obs: se se escribe un valor en la dirección de un dispositivo y luego se se lee su contenido, en general no se obtiene lo mismo que se escribió. ! un dispositivo se disfraza de memoria, pero no se comporte como memoria! 59 Ejemplo 2: visor de calculadora Solución 1 (cara): asignar una dirección de 1 byte a cada dígito. Cada segmento está asociado a un bit dentro del byte. Si se escribe |01011110| en 0xe003 entonces aparecerá b en el cuarto dígito. 4 3 5 6 0 ... 1 2 7 0xe007 ... 0xe001 0xe000 A15-A0 D7-D0 A15-A3 A2-A0 RD WR SEL A! [0xe000, 0xe007] E Decod ... 3x8 WR Latch ... WR Latch ... Obs: al leer la dirección de un dígito no se lee su contenido en el visor (se lee basura), pues esta interfaz solo permite escribir en los latches. Solución 2 (económica): Hacer una interfaz que permita encender no más de un dígito a la vez. Hacer un programa que enciende secuencialmente cada dígito más de 60 veces por segundo. Esto crea la ilusión óptica de que el visor está permanente encendido. Mapeo del visor en memoria: ! 0xe000 indica qué dígito está encendido ! 0xe001 el bitmap de ese dígito Código en C: char bm_digs[8] /* bitmap de cada dígito */ display() { ! char *port_ndig = (char *) 0xe000; ! char *port_bmdig = (char *) 0xe001; ! ! for(int num_digit = 0; ; num_digit = (num_digit+1) % 8) { ! ! *port_ndig = num_digit;! ! ! /* cual dígito */ ! ! *port_bmdig = bm_digs[num_digit];! /* dibuja dígito */ ! ! usleep(1000/(60*8)); } } 60 A15-A0 D7-D0 A15-A2 RD WR A0 D2-D0 SEL A =0xe000, 0xe001] A0 WR Latch B WR Latch A 1 Decod 3x8 E D7-D0 ... a b ... El descodificador entrega casi todas sus líneas en 1 por lo que esos dígitos se apagan. Una sola línea esta en 0 la que corresponde al dígito “iluminado”. Cada una de sus segmentos está conectado a un bit del latch B que controla si enciende o no. Controlador de entrada y salida Problema: Si se quiere mostrar el visor, el programa gasta todo su tiempo en el ciclo display(). Si se dedica a otro cálculo, el visor se apaga ! Solución: Usar un controlador para desplegar el visor. Un controlador es un microprocesador dedicado a una función específica requerida para el funcionamiento de algún dispositivo. La CPU y el controlador i n t e r c a m b i a n información a través de una línea de comunicación. MEM CPU Interfaz de comm Controlador ... Típicas líneas de comunicación: # Serial RS232 ! ! Velocidad 300 a 115,200 bit/s ! ! Distancia hasta 10 metros ! ! Uso: modem, tty, teclado (obsoleto), mouse (obsoleto) ! SATA ! ! Velocidad 1.5 Gbit/s(SATA/150), 3 Gbit/s(SATA/300), 6 Gbit/s(SATA 6 Gbit/s) ! ! Distancia hasta 1 metro ! ! Uso: discos duros, DVD, ... 61 ! ! ! ! ! ! ! ! ! ! ! USB ! Velocidad 1.5 o 12 Mbit/s (1.0), 480 Mbit/s (2.0), 5 Gbit/s (3.0, 3.2 Gbit/s real) ! Distancia oficial hasta 5m (1,0, 2.0), 3m (3.0) ! Cable 5 hilos (1.0, 2.0), 9 hilos con trafico bidirecional (3.0) ! Permite el uso de maximum 5 HUBs ! Uso: Universal (?) FireWire ! Velocidad 400 Mbit/s (FW 400) o 800 Mbit/s (FW800) ! Distancia hasta 4.5m (FW 400), o 100m (FW 800 sobre Cat5e UTP) ! Permite el use de maximum 16 HUBs (FW400) ! Uso: A/V, ej cameras FireWire vs USB: USB es mas barato por que no necesita uso de controlador. Pero en el mundo real USB es mas lento que FireWire por que es limitado por la velocidad del procesador (alcanza +- 280 Mbit/s en uso prolongado). Obs: MB != Mb : MegaByte != Megabit. Para evitar confundir aquí usamos Mbit. Transmisión paralela Puertas paralelas usan 8 líneas de datos para transmitir simultáneamente 8 bits de información. Si se necesita enviar más de 1 byte, se transmiten secuencias de 1 byte. Ventajas (en teoría): • Implementación fácil. • 8x mas rápido que enviar los 8 bits en serie. Desventajas (vida real): • Crosstalk: Interferencia dentro de las 8 señales en el cable produce degradación rápida de los señales. El cable tiene que ser corto. • Skew: Dado a efectos físicos, la velocidad de transferencia de los 8 señales es diferente, entonces no llegan al destino al mismo tiempo. Para reducir/evitar crosstalk hay que blindar las líneas para que lleguen más lejos (típicamente más que 2 metros), lo que incrementa el costo del cable. No se puede llegar a las distancias de cables seriales. No hay solución para evitar skew. El controlador al destino tiene que resguardar todos los bits hasta que llega el ultimo bit del byte. Los problemas de crosstalk y skew crecen cuando la velocidad de comunicación (frecuencia del reloj) esta más alta. Puertas paralelas son obsoletos por el precio y la limitación en longitud del cable. Casi todas las líneas de comunicación hoy en día usan transmisión serial. 62 Transmisión serial La forma más económica para transmitir información es una linea serial. Cada byte se transmite en un “paquete”. Ambas puertas (la del computador y la del dispositivo) se configuran para usar el mismo formato de paquete: • La misma velocidad: (300, 2400, 9600, 19200, 38400, ... 115 200 bit/s) • Igual numero de bits de datos (5, 6, 7, 8) • Igual paridad (sin, par, impar) Detección del comienzo de 1 paquete El estado “natural” de la línea cuando no se transmiten datos es el valor 1.La transición a 0 indica el comienzo de un paquete. El tiempo de transmisión del paquete (start bit + paridad + datos + al menos 1 stop bit) está determinado por la velocidad configurada. Luego la línea tiene que quedar en 1. Una nueva transición a 0 indica el comienzo del nuevo paquete. Envío / Recepción de datos por una puerta serial Un programa opera una puerta serial como si fuera una puerta paralela (+-). Se envían bytes completos escribiendo en una puerta de datos. Es la puerta serial (1 chip) que se encarga de transmitir ese byte bit a bit por un solo cable. Ese chip es muy común y tiene un costo muy bajo. Del mismo modo es la puerta serial la que recibe bit a bit un paquete y lo transforma a un byte que puede ser leído en la puerta de datos. Interfaz con el computador ! SEL A CS A0 Puerta Serial Send Rec GND C/~D RD WR D7-D0 Rec Send GND Controlador D La puerta serial se opera a través de: • Puerta de datos (C/~D = 0): Cuando se escribe/lee en esta puerta se envia/recibe un byte. • Puerta de control (C/~D = 1): Se escribe en esta puerta para configurar número de bits, paridad, etc. Se lee en esta puerta para averiguar el estado de envío / recepción de los datos. 63 En código: char *port_data = (char *) 0xf12c; char *port_ctrl = port_data + 1; *port_ctrl = [...] /* Configurar */ char status = *port_ctrl /* = |??????RT| R = 1 => se puede enviar un byte T = 1 => se recibio un byte*/ void ! ! ! ! } } enviar(char *buff, int n){ while(n--){ ! while(!*port_ctrl&1) /* se puede enviar? */ ! ! ; /* busy wait */ ! *port_data = *buff++; /* envia 1 byte*/ La transmisión de 1 byte toma +-1 ms a 9600 bps, por lo que el ciclo de busy wait se ejecuta 1000 a 10,000 veces antes de poder volver a transmitir. La recepción es similar: void ! ! ! ! } } recibir(char *buff, int n){ while(n--){ ! while(!*port_ctrl&2) /* se puede recibir? */ ! ! ; /* busy wait */ ! *buff++ = *port_data; /* recibe 1 byte*/ Organización de la puerta serial (Chip tipo 16450 UART) Cuando se completa la recepción de 1 byte, se almacena en el latch, esperando a que sea leido por la CPU. Se se recibe un nuevo byte antes de que la CPU lea el previo, se pierde 1 byte. Esto da un tiempo de 1 ms a 9600 bps para leer el byte. Para evitar ese problema se puede reemplazar el latch por un buffer de 16 bytes (16550 UART), lo que multiplica por 16 el plazo de lectura. REC r Shift Register Send Latch r Shift Register Rec B A ! ! ! ! ! B A RDY RD D7- D0 Latch s A B 64 UART = ! Universal ! Asynchronous ! Reciever ! Transmitter Interrupciones (interrupts) Problema: un ciclo de busy-waiting ocupa el 100% de la CPU no se puede realizar ninguna otra actividad. Solución: El mecanismo de interrupciones permite que la CPU puede ejecutar otros procedimientos mientras se espera que un dispositivo esté disponible para enviar o recibir un dato. Intn MEM CPU Int1 Int0 ... Comm RDY Cuando el dispositivo necesita la atención de la CPU, interrumpe el procedimiento en curso, colocando la entrada INTx de la CPU en 1. Con esto la CPU suspende la ejecución del procedimiento en curso e invoca una rutina de atención de interrupciones. Esta rutina se encarga de interactuar con el dispositivo. (Ej: recibir un byte, enviar un nuevo byte.) Luego se retorna el procedimiento suspendido en el punto en donde quedó y en forma transparente, es decir, sin que se entere de que fue interrumpido. Se dice que este tipo de interrupciones ocurre por hardware, al contrario existen interrupciones que se activan por software: se ejecuta una instrucción con código de operación inexistente, se divide por 0, se lee una palabra no alineada, etc. Obs: la instrucción trap en IA86 no existe ! Por ello causa una interrupción. Las interrupciones por hardware pueden ser inhibidas con instrucciones de máquina (ej: enable_int y disable_int). Un bit del registro de estado indica se las interrupciones estan inhibidas o no. En M32 agregamos un bit I al registro de estado ZVSC IZVSC Si I = 1 pueden ocurrir interrupciones por hardware, si I = 0 no pueden ocurrir interrupciones por hardware. Este bit no puede inhibir las interrupciones por software. La dirección de la rutina de atención de interrupciones (por software y hardware) se obtiene de un vector de interrupciones. El vector de interrupciones se ubica típicamente o en una dirección fija dependiente de la arquitectura (ej: 80888 dir 0) o se apunta desde un registro especial en la CPU (ej: 80386). La unidad de control chequea durante el ciclo fetch en estado de la línea Int Si Intn = 1 la unidad de control invoca la interrupción. 65 0 div por 0 +4 cód de op no existente +8 int por hw 1 + 12 int por hw 2 ... ... Para ello hay que resguardar cualquier registro que necesite la rutina de atención y que el procedimiento puede eventualmente estar utilizado. La unidad de control se encarga de PC y SR. La rutina de atención se encarga de Rx. Todos estos registros deben ser restaurados al retomar el procedimiento interrumpido, para evitar su funcionamiento incorrecto. En código el trabajo realizado por la unidad de control (se supone R31 es un stack pointer): R31 := R31 - 8 Memw[R31 + 4] := PC!! !Push PC Memw[R31 + 0] := Extw(SR)!!Push SR SR.I = 0! ! ! ! !inhibe las ints PC := Memw[IVR + 4 + x]! !dir del vec ints + despl de la int [ rutina de atención de interrupciones, termina con RETI ] SR := Memw[R31 + 0]!! PC := Memw[R31 + 4]!! R31 := R31 + 8 !Pop SR (restaura SR.I) !Pop PC Obs: • Para retornar el procedimiento interrumpido, se debe invocar RETI la que restaura correctamente SR y PC • El dispositivo continua colocando INT en 1 hasta que se suprima la causa de la interrupción. Por ello se necesita inhibir las interrupciones antes de invocar la rutina de atención. Sino, después de ejecutar la primera instrucción, se volvería a provocar una nueva interrupción, cayéndose en un ciclo infinito. • Al restaurar SR se rehabilitan las interrupciones nuevamente. • En algunas arquitecturas se puede inhibir selectivamente las interrupciones por hardware. • La rutina de atención puede rehabilitar las interrupciones aún sin terminar de atender su dispositivo. Esto permitiría que otros dispositivos puedan interrumpir la rutina de atención, cuando el tiempo de reacción a una interrupción es critico (en práctica no se hace) • El hecho que la rutina de atención deba resguardar los registros que usa y que retorne con RETI obliga a programarla en assembler (al menor una parte), o a usar un compilador que genera código especial. Ejemplo: transmisión serial usando interrupciones. La puerta serial tiene 2 salidas que no hemos visto. Rutina de atención de INT2: (n = numero de bytes que quedan) P Serial CPU <resguardar registros> while (n > 0 && *port_ctrl & 2) { ! n--; ! *buff++ = *port_data; } if (n == 0) Procesar (); <restaurar registros> RETI 66 Int1 Int2 TxRDY RxRDY Obs: Desde el instante en que el dispositivo sube la interrupción hasta que se llama la rutina de atención, puede pasar bastante tiempo ya que: • las interrupciones pueden estar inhibidas • otros dispositivos pueden pedir interrumpir, pero se atienden de uno a la vez. Por ello se reciben todos los bytes que hayan llegado. Si el buffer interno de la puerta serial se llena antes de la interrupción hay perdida de bytes! Evaluación: Supongamos un CPU lento de 10 MIPS (+- 33 MHz i386) Busy-waiting: mínimo 10 instrucciones por byte transferido. Tasa máxima de transferencia = +- 1 MB/s (1 millón de byte / s), con 100% ocupación de la CPU. Interrupciones: +- 100 instrucciones por byte transferido. Tasa máxima de transferencia = +- 100 KB/s (100 000 byte / s) si hay 1 byte / interrupción. Ocupación de la CPU para una puerta de 2000 byte / s. 100 instr / byte * 2000 byte / s = 200.000 instr / s. = 2% de 10 MIPS = 2% de la CPU El 98% restante se ocupa para otras tareas! Entrada / Salida usando canales DMA Problema: Primero: Consideran un dispositivo USB2 lento que transfiere 16 Mbit/s (USB2 max = 480 Mbit/s) = 2 MB/s. (= +- 2 000 000 bytes / s) 2 000 000 bytes/s * 100 inst/byte = 200 000 000 inst/s = 200 MIPS = 2000 % de la CPU (Con busy-waiting se require el 200 % de la CPU) Segundo: Ciertos dispositivos pierden datos si no se les atiende dentro de un plazo prudente, plazo que puede no cumplirse si se inhiben las interrupciones. Solución: DMA = Direct Memory Access Objetivos: • desligar a la CPU de la transferencia de bloques de datos entre memoria y dispositivos • permitir velocidades de transferencia acotadas por la velocidad de la memoria. Un controlador de DMA es un co-procesador dedicado a transferir datos entre memoria y dispositivos sin parar la CPU. Para que un dispositivo pueda usar DMA su interfaz con el bus debe ser capaz de generar y entender la linea DREQ (Data Request) y DACK (Data Acknowledge). El controlador de DMA tiene un numero fijo de pares DREQ/DACK. Solo se puede conectar un dispositivo por par. 67 Cuando un programa (ej: un driver) necesita hacer una transferencia, configura el DMA con la siguiente información: • dirección de un buffer en memoria • número n de bytes a transferir • numero del par DREQ/DACK que usa el dispositivo • sentido de la transferencia El controlador de DMA posee varias puertas de control por donde recibe esta información. Para transferir los datos ocurre lo siguiento (en resumen): 1. El dispositivo coloca DREQ en 1 2. El controlador DMA pide el bus a la CPU, usando una linea HOLD. 3. La CPU concluye cualquier operación que esta usando el bus, lleva todas sus lineas que lo conectan al bus a tristate, y coloca una linea HOLDA (HOLD ACK) en 1. 4. El DMA transfiere datos: coloca la dirección en el bus de direcciones, RD o WR en 1, y señala al dispositivo que escriba o lea el dato en el bus de datos. (= un acceso simultáneo a la memoria y a una puerta de datos del dispositivo). 5. Si el dispositivo lleva la línea DREQ en 0, el DMA lleva HOLD a 0 y lleva sus líneas a tristate, la CPU lleva HOLDA a 0 y puede volver a ocupar el bus. 6. Sino, se trata de una transferencia en ráfaga (burst transfer), goto 4. Obs: • La CPU y controlador DMA comparten el mismo bus de datos y por lo tanto comparten toda la memoria y dispositivos. Solo uno de ellos puede acceder al bus en un instante dado. • Mientras el DMA ocupa el bus, la CPU puede continuar ejecutando la instrucción de máquina en curso, pero si se necesita acceder al bus debe esperar a que el DMA suelte el bus. • La transferencias que realiza el DMA son absolutamente transparentes para el programa que ejecuta la CPU. Al fin de la transferencia de n bytes, el DMA interrumpe la CPU usando una línea de interrupción. Evaluación: Costo de transferencia de un bloque de n bytes: • programación del DMA (costo fijo) • 1 acceso a memoria y dispositivo por cada byte, por el controlador DMA • 1 interrupción al final del bloque 68 Arquitecturas Avanzadas Memoria Caché La CPU gasta mucho tiempo esperando el envío de datos desde y hacia la memoria. Memoria más rápida implique un CPU mas rápido. Como tener acceso rápida a la memoria? Memoria dinámica (DRAM): económico, pero lenta. Memoria estática (SRAM): rápida, pero cara. Experimento: • Descomponer la memoria requerida por un programa en líneas de 4, 16, 32 o 64 bytes de direcciones contiguas. • Contabilizar el número de accesos a cada una de las líneas durante un intervalo de ejecución de un programa. • Hacer un ranking de las líneas más accesadas. Hecho empírico 1: El 98% de los accesos se concentran en el 3% de las líneas. Localidad espacial: si se hace un acceso a un elemento de memoria, hay alta probabilidad que se hace un otro acceso a elementos que se encuentran ʻcercaʼ en el futuro próximo. Usando este fenómeno podemos tener accesos mas rápidas a la memoria y entonces un CPU mas rápido. Idea 1: Reservar una memoria estática (el caché) de pequeño tamaño (Sc) para guardar las líneas mas accesadas ahora. El resto se almacena en le memoria dinámica (Tamaño Sm, con Sc << Sm). Problema: Cómo saber cuales serán las líneas más accesadas durante el intervalo de ejecucion? Hecho empírico 2: un buen predictor de las líneas que serán más accesadas en el futuro cercano es el conjunto de las líneas que fueron accesadas en el pasado más reciente Localidad temporal: si se hace un acceso a un elemento de memoria, hay alta probabilidad que se hace un otro acceso a este elemento en el futuro próximo. 69 Idea 2: Guardar en el caché las últimas líneas de la memoria que hayan sido accesadas. Supongamos: Tc = Tiempo acceso caché ! = +- 10ms, Tm = Tiempo acceso memoria ! = 70ms % de exito = hit rate = hr ! = 90 → 99 % Memoria Caché N bytes N No de linea 2N 4 3N 20 4N 1 ... 1021 ... ... T accesso: Tc <= hr * Tc + (1-hr)*Tm << Tm El caché contiene una aproximación de las líneas más frecuentemente accesadas. Se requiere un campo adicional que almacene el número de línea que se guarda en una línea del cache. Costo = Sc * Costo(SRAM) + Sm * Costo (DRAM) Costo (DRAM) predomina. Problema: al accesar la memoria hay que “buscar” el número de linea en el caché. Para que sea eficiente hay que buscar “en paralelo” muy caro. Grados de asociatividad de un caché: • full: una línea de la memoria puede ir en cualquier línea del caché • 1: si el caché tiene Lc líneas, la línea l de la memoria solo puede ir en la línea l mod Lc del caché 2: se usan 2 caches paralelos de 1 grado de asociatividad • • 4: se usan 4 caches paralelos de 1 grado de asociatividad • ... Lectura en caché de grado de asociatividad 1 Al leer la línea l se examina la etiqueta n de la línea l mod Lc del caché. • Si l = n éxito • Si l != n fracaso. Hay que recuperar la línea de la memoria dinámica y reemplazar la línea n por la l Grado mas alto: verificar l = n en todos los caches. Obs: • A igual tamaño de caché a mayor número de grados de asociatividad mejor tasa de aciertos, pero mayor el costo. • A igual grado de asociatividad, a mayor tamaño del caché, mejor la tasa de aciertos. • En la práctica, a igual costo el óptimo en la tasa de aciertos se alcanza en 1, 2, 4 (a 8) grados de asociatividad. La estadística dice que se hacen 3 lecturas por una escritura. Políticas para la escritura: • Write-through: las escrituras se hacen en el caché y en la memoria simultáneamente, pero pueden hacerse sin bloquear a la CPU. • Write-back: una escritura sólo actualiza el caché. La memoria se actualiza cuando corresponde reemplazar esa línea por otra. 70 Problemas de un caché de 1 grado de asociatividad. Ej. cache de 1024 bytes (1K) char buff [128*1024] for (;;) ! buff[0] + buff[64*1024] ! /* línea mem buff[0] = línea mem buff[64*1024] en el caché */ Tasa de aciertos con respectos a los datos = 0% En la práctica los programa no exhiben comportamientos tan desfavorables.La eficiencia del caché dependerá de la localidad en los accesos del programa que se ejecuta. Esta localidad depende de las estructuras de datos que maneja el programa. De mayor a menor localidad: • La pila: el acceso a variables locales tiene un hit rate que supera al 99% • El código presente muy buena localidad • El acceso secuencial de grandes arreglos o matrices (que superan el tamaño del caché) tienen mala localidad. Obs: Si durante un ciclo prolongado las variables locales y el código del ciclo se mapean en el mismo lugar en el caché, el tiempo de ejecución puede caer hasta un 50%. Este problema no ocurre en un caché de 2 grados de asociatividad, en donde si se pueden mantener en el caché 2 líneas que se mapean en el misimo sitio. Otra solución es tener un caché separado para código (instruction cache) y un separado para datos (data cache). Jerarquía de memoria: Capacidad Desempeño Registros 0 ciclos Caché L1 1 ciclos (También en la jerarquía hay discos duros, DVD ... tiempos son de milisegundos hasta segundos) Caché L2 2 - 3 ciclos Memoria dinámica +-7 ciclos Jerarquía de buses Una vez resuelto el problema de la velocidad de la memoria, el bus se transforma en el siguiente cuello de botella: Se pueden hacer buses económicos en donde es posible conectar CPU, memoria, DMA y todos los dispositivos. Sin embargo el resultado es un bus muy lento por que las señales tardan demasiado en propagarse. En efecto, para que las señales lleguen a todas las componentes es necesario agragar buffer y transciever que amplifican las señales, pero a su vez introducen un tiempo de retardo adicional. Por otro lado, se pueden hacer buses rápidos, pero que operan a distancias cortas (+- 10 → 20 cm) y con un número limitado de componentes conectadas al bus. 71 Solución: Jerarquizar Las transferencias entre componentes se pueden ordenar: ! CPU ! ! ! Cache ! ! ! 98% ! Caché ! ! Memoria! ! ! ! CPU! ! ! Frame Buffer simple ! Memoria! ! Disco ! Memoria! ! Red ! CPU o mem! ! 90% (del resto) Otros dispositivos Se crean buses especiales para cada una de estos niveles. Ejemplo: Pentium II, III, IV, Core 2 , Core 2 Duo Front Side Bus 800 Mhz * 64 bit +- 6GB/s CPU Caché PCIex 3.2GB/s SATA 150MB/s Northbridge (Memory Hub) IDE 100MB/s Southbridge (IO Hub) DIMMs 2 canales de +- 6GB/s Bus Proprietario PCI 120MB/s PCIe (4) 200MB/s USB Flash BIOS 72 Pipelining Una forma de mejorar el rendimiento de M32 es hacer que la carga de la siguiente instrucción se efectúe al mismo tiempo que la ejecución de la instrucción actual, si ésta no ocupa el bus. add F 1 F 2 sub ldw add D E F1 F2 D E F1 F2 D E1 E2 (1) F1 F2 D E be F1 F2 add (2) D E F1 F2 F1 F2 D E (1) Solo se puede hacer un acceso a memoria a la vez. En este caso, load necesita accesar la memoria, por lo que la carga de la siguiente instrucción no puede hacerse en pipelining. Sin embargo, los microprocesadores modernos incorporan dos buses: uno para datos y otro para instrucciones (arquitectura de Harvard). En este caso en (1) sí se puede cargar la siguiente instrucción en pipelining, siempre y cuando la instrucción este en el caché. Cache D CPU Cache I Bus Snooping (2) La siguiente instrucción se carga, pero no sirve, ya que el salto condicional previo se efectúa. Para evitar que se tenga que botar instrucciones ya cargadas, en SPARC y otras arquitecturas se especifique que los saltos son retardadas (delayed branch). La instrucción que sigue un salto se ejecuta aún cuando ocurra el salto. Para implementar esto se usan dos circuitos secuenciales: • La unidad de ejecución: decodifica y ejecuta las instrucciones • La unidad de fetch: carga la siguiente instrucción Niveles de pipelining: en la práctica se emplean más de 2 unidades para lograr ejecutar 1 instrucción por ciclo del reloj. 73 add ,,R1 sub R1,, ldw [],R2 add R2,, be F D E S F D E S F D E1 F (1) M D (2) F ... S E D (3) F ... S = Guardar en Registro M = Acceso a Memoria S E D (3) F (4) E S D E D ... F add F S (1) se usa el registro calculada en la instrucción anterior, que todavía no termina de actualizar. Register Bypassing es una técnica que lleva el resultado de una operación directamente a uno de los operadores de la siguiente instrucción. (2) el valor de R2 se conocerá el final del ciclo M, por lo que se introduce un ciclo de relleno a la espera de R2. (Pipeline Stall, Data Hazard) Register Scoreboarding permite acordarse de los registros con valor desconocido. (3) Se debe esperar a que la unidad de ejecución se desocupe. (Pipeline Stall, Structural Hazard) (4) se produce el salto por lo que se descarta el trabajo hecho con las 3 siguientes instrucciones. Los saltos son dañinos para la efectividad del pipeline (Branch Hazard) Ejemplos: Pentium, SuperSparc: 5 niveles, Pentium III: 10 niveles, P4: 20 → 31, Core: 14 Con altos niveles de pipeline el trabajo que se descarta es inaceptable. Branch prediction es una técnica que intenta de predecir saltos. Por ejemplo los saltos se almacenan en un Branch History Table. Al hacer la descodificación de una instrucción de salto almacenada se sigue cargando instrucciones a partir de la dirección de destino. Actualmente los CPU logran predecir correctamente más del 95% de los saltos. Todas estas técnicas permiten acercarse a la barrera de 1 instrucción por tick. 74 Arquitecturas Superescalares Se sobrepase la barrera de 1 instrucción por ciclo. Ejemplo: superescalar de grado 2. La idea fundamental es que se colocan 2 pipelines en paralelo: • se cargan 2 instrucciones a la vez • se descodifican 2 instrucciones a la vez • se ejecutan 2 instrucciones a la vez • se actualizan 2 registros a la vez El problema ocurre si en ejecución hay una dependencia entre las 2 instrucciones. 1 2 3 4 5 add add add add sub R1, R2, R3 R5, 8, R7! R9, R10, R11! R11, 2, R7 ! R13, 18, R9! ! ! ! ! ! ! ! ! 1 2 3 4 y y y y 2 3 4 5 no dependen no dependen dependen: R11 no dependen 3 y 4 no pueden ejecutarse en paralelo. En la etapa de ejecución se analizan las dependencias de ambas instrucciones. Si el resultado de la primera instrucción es un operando de la segunda, le ejecución de la segunda se descarta y se repite en el ciclo siguiente, para garantizar la compatibilidad con un procesador non-superescalar. Los compiladores agregan una pasada que reordena las instrucciones de modo que no existen dependencias de datos. Ventajas: • 1 < tasas de instrucciones por ciclo <= numero de pipelines Desventajas: • Mucho mas alta complejidad de la CPU. • Hay que recompilar para limitar las dependencias. Ejemplos: • 486: 1 pipeline 4 etapas (stages) • Pentium: 2 pipelines 5 etapas • Pentium Pro → Pentium III: 3 pipelines 10 etapas 75 Ejecución fuera de orden Problema: Compiladores pueden reordenar las instrucciones, pro les falta conocimiento dinámico acerca de cuanto tomará un load, et cetera ... Solución: ejecución fuera de orden. Una dependencia de datos bloquea la instrucción involucrada, pero no las que vienen a continuación si no existe dependencias. Se usa desde el Pentium pro. load[R1+10],R2 F D A add R2,R3,R4 F D A add R3,10,R5 F D E S F D A E M M M M S R E Retiro en orden S R Analisis add R4,1,R6 R E S S R R 3 pipelines Con register renaming: load[R1+10],R2 load[R1+10],R2 F D A E M add R2,R3,R4 add R2,R3,R4 F D A add R5,20,R2 add R5,20,R2' F D A E S add R2,8,R2 add R2',8,R2" F D A E S R S R M E S R R S R anti-dependencia Ejecución Especulativa load[R1+4],R2 F D A cmp R2,8 F D A bgt L F D A E M M E E R B add R3,10,R4 F Predicción correcta ... L:add R4,1,R4' R F D A E S D ... Predicción mala R En caso de no branch se restauran los nombres de los registros de modo que R4 retorna un valor original. 76 Ejemplo: Pentium III • Traducción de 1 instrucción CISC a 1 o 2 instrucciones RISC • 2 ALUs enteros de 32 bits (2 mult + 2 suma x ciclo) • 1 mult FP de 64 bits (1 mult + 1 suma x ciclo) • 1 sumador de 64 bits (1 suma x ciclo) • 1 load/store unit • 1 jump unit • 3 instrucción decodificacion x ciclo • 40 instrucciones pendientes, pipeline de 10 etapas Ventajas: • Velocidad hasta x2 , > tasa de instrucciones x ciclo • No es necesario recompilar • Permite realizar accesos concurrentes a la memoria Desventajas: • Más consumo por instrucción ejecutada: uso de energía hasta x10 • Mucho mucho más alta complejidad de la CPU • Frecuencia mas baja 77 Multi-Core chips Ejemplo: Intel Core 2 Nehalem, Core i5/i7, Clock 2.6 -> 3.6 Ghz, 8 Mb Cache (Sept 2009) Obs: Core i5/i7 también es especial por que cambia Jerarquía de buses: primero x86 intel con memory controller en el chip mismo (AMD hace algo similar desde 2003). Multi-Core chips son una versión de Symmetric Multi Processing (SMP): una arquitectura usando varios CPU en un computador, donde todos los CPU están conectados a una sola memoria central. Eso permite a cada CPU de ejecutar un programa y trabajar con datos en cualquier lugar de memoria. Un chip multi-core contiene varios core, cada un CPU completo, con incluso sus propios L1 y L2 cache (salvo primeras implementaciones). Un chip N-core puede correr N hilos de ejecución en paralelo, siempre si no usan recursos compartidos. Consideran que el bus ya es lento para 1 CPU, con varios CPU el problema es peor! Opciones diseño multicore: • Revolución: simplificar cada core y tener muchos cores en arquitectura masivamente paralelo. Ejemplo: IBM Cell (1 + 8 cores), Sun UltraSparc T1 (8 cores) son sin ejecución fuera de orden. • Evolución: minimizar uso de energía y usar la ley de Moore para poner mas transistores en el mismo chip. Ejemplo: Intel Core Cache Coherency Todos los core tienen que ver la misma memoria. Los varios core pueden tener la misma linea de memoria en sus propios caches. Que pasa si un core escribe un valor en su cache (L1 o L2)? No es factible de dar acceso directo y completo desde el cache de un CPU a otro CPU en sistemas SMP (por velocidad baja del bus). Se necesita un protocolo de ʻcache coherencyʼ. (Se necesita para cada implementación de SMP). 78 Implementación simple con write-through caches: • Cada core Ci detecta cuando otro core Co escribe un valor en su cache. • Si esta valor también esta en el cache de Ci, esta linea esta marcada ʻsuciaʼ. • Leer una linea ʻsuciaʼ requiere cargar el nuevo valor desde memoria central. Implementación mas sofisticada usando ʻbus snoopingʼ: • Leer una linea ʻsuciaʼ significa que la memoria central no esta up to date • El core Co ve que el core Ci lee el valor cambiado usando bus snooping y provee el nueva valor directamente a Ci. En ese momento el memory controller también puede escribir esta valor en memoria Implementación standard es MESI cache coherency protocol (Modified, Exclusive, Shared, Invalid). El nombre viene de los 4 estados en que puede ser una linea del cache: •Modified: The local processor has modified the cache line. This also implies it is the only copy in any cache. •Exclusive: The cache line is not modified but known to not be loaded into any other processor's cache. •Shared: The cache line is not modified and might exist in another processor's cache. •Invalid: The cache line is invalid, i.e., unused. (Start state) Mirando el bus de direcciones y algunos otros señales de control, se puede determinar las transiciones de un estado a otro. Una operación especial: “Request For Ownership” (RFO) • cuando un otro core quiere escribir una linea con status (local) Modified, este core manda los datos al otro core y marca la linea (local) como Invalid • cuando este core quiere escribir una linea con status Shared, primero los otros core lo marcan como Invalid 79