Microarquitectura: explicando los entresijos de un procesador – Parte 1/2

En este artículo ampliaré la información que ya mostré en artículos previos. En ellos compartí contigo algunos conceptos como la ISA, la microarquitectura, partes, diseño, etc. Ahora, lo haré de una forma algo más práctica, ya que esos artículos previos fueron muy teóricos.

Para ello, me serviré de una microarquitectura moderna para ir «despedazando» parte a parte e intentar explicar cómo funciona cada parte y también el conjunto completo. De ese modo entenderás mejor cómo funciona una CPU. Con ayuda de algunos diagramas de flujo, datapaths, y die shots será muy más intuitivo…

Conceptos básicos

Los conceptos básicos que necesitas adquirir para comprender el resto de partes de este artículo son:

  • High level: es el programa, el software. Es decir el código con instrucciones y datos que se deben procesar para ejecutar un programa informático. Gracias al os lenguajes de programación de alto nivel, los desarrolladores no tienen que aprender sobre la arquitectónica de un procesador, lenguaje máquina, o lenguaje ensamblador (ASM).
  • Low level: a más bajo nivel se tienen los estados, RTL. Si lo recuerdas que lo he mostrado en artículos previos de diseño de una CPU. Dentro del bajo nivel se tiene la implementación electrónica de una ISA, es decir, la microarquitectura. A su vez compuesta por:
    • Control: (véase siguiente apartado)
    • Datapath: (véase siguiente apartado)
  • ISA: ya hablé de ella aquí, es la interfaz entre software/hardware, es decir, el repertorio de instrucciones que los programas pueden usar para realizar todas las operaciones. Recuerda que para una misma ISA se pueden tener diversas microarquitecturas que la puedan implementar. En cambio, una misma microarquitectura no puede ejecutar varias ISAs diferentes, a no ser que sea por traducción por hardware, por traducción en tiempo de ejecución, por emulación…
  • Diagrama de bloques (die map): es el diagrama que representa a una microarquitectura. Es decir, el flujo que recorren los datos/instrucciones y las unidades que tiene implementadas la microarquitectura. Así se puede entender cómo son procesadas.
  • Die shot o micrografía: es una fotografía tomada a un die o dado. Es decir, cómo se ve el propio chip. Debido a la complejidad de los actuales chips, no se pueden determinar demasiados detalles. Pero hace décadas se podía hacer ingeniería inversa solo con ver estas imágenes, ya que se podían apreciar bien los transistores labrados en el sustrato o las interconexiones metálicas que los conectaban para formar el circuito electrónico. No obstante, con algunos se pueden ver algunos detalles interesantes con ayuda del diagrama de bloques y la micrografía para que puedas apreciar mucho más a fondo cómo trabaja tu CPU. Pero esto lo dejo para el siguiente artículo…

Eso sería resumido para que lo tengas presente. Aunque, como ya he dicho, lo tienes en artículos previos si necesitas refrescar conceptos….

Control & Datapath

datapath-control

Me gustaría detenerme especialmente en dos conceptos muy importantes, y de los que no he escrito en anteriores artículos. Esos dos conceptos son:

  • Control: la zona de control es la zona o camino de una microarquitectura donde se manejan las instrucciones. De ese modo, se puede gobernar y coordinar el funcionamiento de cada una de las partes de la CPU. Es lo que he pintado de rosa en el dibujo que hice. A él pertenecen partes como el bus de control, bus de instrucciones, unidad de control, pila, registro de estado, registro de instrucciones, y el registro contador de programa (PC), etc.
  • Datapath: las señales de control anteriores (microórdenes o microinstrucciones) que genera la unidad de control (cableada o de microcódigo) serán aplicadas sobre el camino de datos. Es decir, los datos sobre los que se opera, la que he pintado de azul. Dentro de ella se encuentran toda la ventana de registros para datos, bus de datos, y todas las unidades de ejecución (ALU, FPU,…).

Por  otro lado, si no tienes demasiada idea de cómo funciona la CPU internamente, deberías también leer estas breves descripciones de algunas de las partes que mostraré en el siguiente apartado:

  • Memoria cache: es una memoria intermedia en la que se almacenan datos e instrucciones que se han traído desde la memoria principal (RAM) para que su acceso sea más rápido en caso de ser necesario más adelante. La CPU buscará la instrucción/dato primero en la cache L1, si está presente se produce un acierto (y se usará), y si no lo está se produce un fallo. Cuando se produce el fallo se pasará a buscar en el siguiente nivel (L2). Si está se usará, si no está se pasa al siguiente nivel (si existe L3) o a la memoria principal.
  • Ciclo de instrucción: existen el llamado ciclo de instrucción en toda CPU. Básicamente se realiza mediante los pasos fetch, decode, y execution, aunque algunas pueden agregar alguno adicional. Por el momento, con esos sería suficiente.
    • Búsqueda: el fetch buscará la instrucción o instrucciones necesarias para seguir con la ejecución del programa que está procesando la CPU. Para eso hará uso de varias partes de la CPU que localizarán dicha instrucción en cualquiera de los niveles de memoria y la traerán, así como los datos necesarios. La instrucción que busca será la de la dirección que se esté apuntando desde el registro PC o Program Counter (registro que siempre apunta al a próxima dirección donde está la siguiente instrucción de la secuencia del programa a ejecutar). Una vez traída la instrucción se almacenará en el IR (Instruction Register), y estará lista para la siguiente etapa.
    • Decodificación: en esta etapa, el decodificador interpretará la instrucción del IR y que pertenece al repertorio de la ISA que usa la CPU. Dependiendo del modo de direccionamiento que se use, el dato o datos necesarios para operar (operadores) podrían venir implícitos en el propio código de la instrucción, por lo que estaría todo listo para enviarlos a unos registros y que una unidad de ejecución hiciese sobre dicho dato o datos la operación. Si no es así, durante este ciclo también se leerá la dirección efectiva donde se localizan los datos y se buscarán como se hizo con la instrucción (en cache o memoria principal o desde E/S).
    • Ejecución: ahora que los datos e instrucción están, solo faltará que las señales de control de la unidad de control de la CPU gobiernen las diferentes unidades de ejecución (ALU, FPU,…) para que ejecuten la operación que indica la instrucción. Por ejemplo, si es una instrucción ADD (suma), la ALU debería sumar los datos para obtener el resultado.
    • Al final, se vuelve a repetir el ciclo de nuevo con la siguiente instrucción (PC+1), se iniciará el fetching, luego decode, execution, y así…
  • TLB: el buffer de traducción anticipada es una pieza clave que trabaja junto con la MMU para buscar lo que se necesita en caso de que la CPU no pueda encontrarlo (fallo). En caso de no encontrar la instrucción o dato, será el sistema operativo el que se ocupe de gestionarlo.
  • MMU: son las siglas de Memory Management Unit o unidad de gestión de memoria. Dicho de forma simplificada, maneja los accesos dentro de todo el espacio de direccionamiento de la CPU. También será el encargado de traducir las direcciones lógicas (memoria virtual) a físicas o reales, así como proteger ciertas áreas reservadas de la memoria. Si la dirección se localiza en el TLB (hit), se traduce la dirección y se le entrega a la CPU para que pueda trabajar con ella. En caso de no estar en el TLB (fault), la MMU buscará en la tabla de páginas del proceso en ejecución y se devolverá la dirección física. Puede suceder que se produzca un fallo de página,  que es lo que he comentado de lo que se debería encargar el SO (por ejemplo, en Unix/Linux, mediante algoritmos sacaría algunas porciones de la memoria principal y dejaría hueco para subir desde SWAP a la RAM lo que se está pidiendo).
  • Predictor: las instrucciones de un programa no siempre son secuenciales, en ocasiones puede haber instrucciones de salto condicional, por ejemplo. Antiguamente, el microprocesador detenía la ejecución y esperaba a que se diese el salto para saber la siguiente instrucción a ejecutar. En los microprocesadores modernos se usan predictores que mediante estadísticas deberían saber a qué instrucción saltaría. De esa forma, el microprocesador puede realizar una ejecución especulativa y seguir sin dejar tempos muertos (burbujas). Mejora el rendimiento, a excepción de que el salto no se haya predicho de forma acertada. En tal caso, habrá que vaciar el cauce de la CPU (tendría penalización) y procesar la instrucción adecuada. Pero si se acierta, entonces se habrá «adelantado trabajo».
  • Ejecución en orden vs OoOE: dicho a groso modo, los microprocesadores más simples usan ejecución en orden. Es decir, respetan la secuencia del programa que ejecutan. Pero para mejorar el rendimiento, los más avanzados han implementado una OoOE (Out-of-Order Execution). Eso quiere decir que se pueden introducir otras instrucciones de forma desordenada para procesarlas, y así mejorar el rendimiento. Por ejemplo, imagina que un programa se compone de las instrucciones A (x=2+6), B (y=x·3) y C (z=8+1). Y que B depende del resultado de A, pero C no tiene dependencias. Entonces se podría ejecutar A y mientras ejecutar también C. Luego, una vez se sabe el resultado de A se puede procesar B.  Incluso puede que los datos no estén preparados por otros motivos, lo que la ejeución fuera de orden evitaría unidades de ejecución en estado idle. Eso ayuda mucho en los casos de microarquitecturas superescalares, segmentadas, o con paralelismo de algún tipo…  Para que la OoOE sea posible, se pueden seguir dos algoritmos (sin profundizar demasiado, ya que necesitaría muchos artículos como este…):
    • Scoreboard o marcador: es un algoritmo de planificación dinámica sin renombrado de registros. Eso hace que se resuelvan dependencias WAW y WAR deteniendo la ejecución (hablaré de ellas en otro artículo). Este sistema fue usado en máquinas antiguas.
    • Tomasulo: es el que se usan en los modernos microprocesadores. Aquí si que se usa renombre de registros, por lo que no se debe detener ante esas dependencias, con un incremento del rendimiento. Para ello, hay que dotar a la microarquitectura de una unidad de alimentación/decodificación (emisión) como verás en el siguiente apartado. Ésta alimentará a unas estaciones de reserva, que no son más que unos registros o colas que mantienen las instrucciones en espera de ser ejecutadas por las unidades funcionales o de ejecución. Luego, una vez se procesa la instrucción conforme van quedando desocupadas las unidades funcionales, entonces se obtiene el dato. Se escribe. Al no estar en orden que sigue la secuencia del programa, un ROB o registro de reordenamiento será el que ponga orden en los resultados obtenidos y irá marcando estado (finalizado, en ejecución, emitida). De esa forma, matienen
  • Register renaming: el renombre de registros es una técnica que permite a la CPU tener un mayor número de registros reales con respecto a los registros lógicos definidos para la arquitectura. Eso permite que cuando se produzca un conflicto se pueda asignar un registro real libre y se marque una relación entre dicho registro y el lógico. Algo así como los enlaces simbólicos de Linux, pero en hardware…

Aunque lo he resumido mucho (me gustaría ir explicando cada uno de esos temas de forma detenida en futuros artículos, pero cada uno da para mucho), creo que con ésto ya deberías tener suficiente para comprender lo que sigue…

Comenzando por una microarquitectura simple

Realmente no es una microarquitectura simple, es bastante avanzada. Pero al menos se trata de una CPU singlecore y algo más antigua para que puedas ver el diagrama de bloques y la microarquitectura plasmada en el die shot, con todas las partes explicadas…

Puedes abrir la imagen del dieshot con los números y el diagrama de bloques de la microarquitectura e ir a la par leyendo cada parte que explico para que puedas comprenderlo y visualizar dónde se situaría tanto en el diagrama como en el die shot.

He parcelado un die shot de un Pentium III (die Coppermine) y he adjuntado también el diagrama de bloques de la microarquitectura P6 en la que se basa. La P6 se usó también para Celeron/Pentium II/Pentium II Xeon, así como Celeron/Pentium III/Pentium III Xeon, para los Mobile, etc-, por tanto, hubo varias variaciones de esta microarquitectura. Además, dentro del Pentium III, también hubo cambios con los núcleos: Katmai, Coppermine, Coppermine-T y Tualatin. Y ahora procedo a explicar cada parte que he numerado:

  1. Cache L2: es la cache de nivel 2 unificada para almacenar datos e instrucciones usadas que se pueden usar más adelante. Ahorrando así ciclos de latencia debido tenerlos que buscar en la RAM. De no encontrarse, se producirá un fallo del TLB y se tendrá que buscar en un nivel superior de memoria…
  2. Cache L1D (DCU o Data Cache Unit): es la cache de nivel 1 para almacenar datos. En este nivel sí que se hace distinción entre instrucciones y datos. Esta unidad se encuentra más cerca de las unidades de procesamiento. Por supuesto incluye la lógica necesaria.
  3. Cache L1I (IFU o Instruction Fetch Unit): es la memoria cache de primer nivel para instrucciones. En esta unidad funcional también se integra la lógica para la unidad de búsqueda (fetch), el ITBL, etc.
  4. TAB (Test Access Port): para hacer pruebas JTAG.
  5. Allocator: actúa como una interfaz entre el cauce en orden y fuera de orden.
  6. BAC (Branch Address Calculator): unidad destinad al cálculo de direcciones.
  7. BTB (Branch Target Buffer): sirve para la unidad de predicción de saltos y es una pequeña memoria que almacena la dirección de la siguiente instrucción que sigue al salto.
  8. APIC (Advanced Programmable Interrupt Controller): gestiona cuando se tiene un sistema multiprocesador, controlando el sistema y las interrupciones.
  9. EBL (External Bus Logic): lógica de control para el bus interno que se relaciona con la placa base. Se incluye aquí el divisor de frecuencia para acoplar la señal saliente a la placa.
  10. Lógica de entrada de la señal de reloj para la distribución a través de todo el dado.
  11. BBL (Backside Bus Logic): se encarga de controlar la comunicación con la cache L2 y el exterior.
  12. DTLB (Data Translation Lookaside Buffer): una parte muy importante para la tabla de paginación, es decir, para relacionar direcciones lógicas y físicas (memoria virtual). Trabajará estrechamente con la unidad de gestión de memoria (MMU).  Cuando un dato no se encuentra en la cache, se produce un fallo y se buscará en un nivel superior hasta encontrarlo y traerlo para su procesamiento… Igual ocurre con el ITLB integrado en el bloque de la cache IL1, pero para instrucciones.
  13. PMH (Page Miss Handler): es la unidad que se activa cuando los datos no están disponibles para buscarlos en otra página de memoria.
  14. MOB (Memory Ordering Buffer): es el buffer de ordenamiento para una arquitectura fuera de orden como es esta.
  15. RAT (Register Alias Table): unidad que mantiene un mapa de registros que se encuentran en la CPU cuando se usa el renombramiento de registros.
  16. Packed FPU: es una FPU dedicada al cálculo de set de extensiones MMX y SSE. Incluyen operaciones tanto de enteros como de coma flotante, pero de forma vectorial.
  17. IEU (Interger Execution Unit): es la ALU dedicada para enteros, es decir, una unidad aritmetico-lógica para realizar cálculos.
  18. FAU (Floating-Point Execution Unit): es otra FPU para operar con números de coma flotante.
  19. MIU (Memory Interface Unit): se encarga de gestionar el bus que mantiene el contacto entre la CPU y la RAM.
  20. SIMD: es otra unidad de cálculo para manejar multiples datos con una sola instrucción. Así se consigue mayor nivel de paralelismo de datos.
  21. RS (Reservation Station): obvio que al ser un procesador con ejecución fuera de orden necesita estas estaciones de reserva al seguir un esquema de Tomasulo. También se conoce como Unified Scheduler, e irá programando de forma dinámica las instrucciones a procesar.
  22. ID (Instruction Decode): decodifica las instrucciones. Traducirá las instrucciones x86 en este caso a operaciones o comandos que puede realizar las diferentes unidades funcionales de la CPU para ejecutar la orden implícita que tiene dicha instrucción. En esta microarquitectura P6, las instrucciones CISC x86-32 (IA-32) se traducen a microoperaciones de 72-bit similares a las MIPS (RISC). Entonces enviará las microoperaciones traducidas a las estaciones de resera y al buffer de reordenamiento. Cada instrucción CISC podría necesitar entre 1 y 4 microoperaciones para completarse.
  23. ROB (Reorder Buffer): es el registro de re-ordenamiento para que las unidades de ejecución fuera de orden operen de forma adecuada e independiente al orden secuencial del programa y luego se obtengan los resultados adecuadamente. En este caso, guardará los resultados que no han sido guardados en otros registros visibles.
  24. MS (Microinstruction Sequencer): es el secuenciador de instrucciones. Otra de las partes de la unidad de control y que genera las direcciones necesarias para recorrer el microprograma o microcódigo almacenado en una ROM. Ten en cuenta que la unidad de control aparece aquí dividida por varias zonas compuestan por esta zona y otras de predicción de saltos y tratamiento, algunos registros para instrucciones y PC, etc.

En el siguiente artículo paso directamente a usar un una microarquitectura moderna y analizo algunas claves de mejora, ahora que ya con éste te debería haber quedado más o menos claro la composición fundamental…

Isaac

Apasionado de la computación y la tecnología en general. Siempre intentando desaprender para apreHender.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

A %d blogueros les gusta esto:

Si continuas utilizando este sitio aceptas el uso de cookies. más información

Los ajustes de cookies de esta web están configurados para "permitir cookies" y así ofrecerte la mejor experiencia de navegación posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estarás dando tu consentimiento a esto.

Cerrar