CC4301 Arquitectura de Computadores

Anuncio
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
Descargar