Uno de los elementos fundamentales en todo sistema informático es la CPU, sin ella no se podrían ejecutar instrucciones y es por ello que es su unidad central. Pero, ¿cómo funciona? En este artículo de conceptos básicos os explicaremos punto por punto lo que hace el microprocesador central de tu PC. Así como detalles que muy probablemente desconocías. Si quieres saber más, sigue leyendo.
¿Qué es una CPU?
Una CPU, de las siglas en inglés Central Processing Unit, es el chip principal que hay en tu PC, consola o teléfono móvil. A día de hoy se presenta de forma compleja, en un solo chip o empaquetado compartiendo espacio no solo con otras CPU, sino muchas veces con otros componentes accesorios a estas y que son clave para el sistema, como puede ser la GPU para realizar gráficos, el controlador de periféricos e incluso el controlador de memoria para acceder a la misma. Si somos estrictos, cuando nos hablan por ejemplo de un microprocesador o una CPU de 8 núcleos, por ejemplo, cada uno de esos núcleos es en sí mismo una CPU o microprocesador completo.
El trabajo de toda unidad central de proceso es ejecutar instrucciones de un programa informático de forma secuencial, para ello funciona por cada instrucción en 5 etapas que son:
- FETCH: captación de instrucciones.
- BRANCH: predicción de saltos
- DECODE: decodificación de instrucciones.
- ISSUE: se encarga de enviar las instrucciones a sus unidades correspondientes para la ejecución.
- EXECUTE: ejecución de instrucciones.
- WRITE BACK: escribe el resultado de vuelta, ya sea en un registro, en memoria o en la caché
Las CPU no se encargan de una sola etapa a la vez, sino que internamente están pensadas para que como mínimo cada una de las etapas del pipeline se ejecuten a la vez. El tiempo que tarda una instrucción para pasar de una etapa a otra es de un ciclo de reloj, y la cantidad de ciclos por segundo se da en forma de frecuencia en las especificaciones del procesador en forma de MHz o GHz.
FETCH (Captación de instrucciones)
En un mundo ideal, la memoria RAM, la cual almacena temporalmente el programa en sus celdas y la CPU, serían una sola cosa. La RAM es necesaria para la CPU, no solo para poder acceder a los programas que ha de ejecutar, sino también para hacerlo a la mayor velocidad posible. No obstante, no explicaremos el punto de vista de la memoria del sistema en esta entrada, más que nada por el hecho que ya entra fuera del espacio de cómo funciona la CPU, aunque formen parte de un todo y tengan una relación simbiótica.
Al proceso de captar instrucciones en una CPU se le llama FETCH, y se basa en una serie de unidades internas que realizan tareas muy específicas. El microprocesador central no se dedica a ir a buscar él mismo los datos y las instrucciones, sino que hace uso de otros componentes del sistema como es el controlador de memoria para hacer dos peticiones simples:
- Lectura del contenido de una dirección de memoria.
- Modificar (escribir) una parte de la memoria en concreto.
Ahora bien, la CPU no se queda de brazos cruzados, sino que tiene una serie de unidades encargadas en el proceso de FETCH que son comunes en todos ellos. Se trata de una serie de registros de funcionamiento específico, siendo estas pequeñas memorias dentro de la CPU de muy pequeño tamaño y de acceso inmediato.
Registros para captar datos
El contador de programa es una memoria que trae en su interior la siguiente instrucción que la CPU leerá de la memoria RAM, por lo que cada vez que se ejecute esta aumentará en uno. No obstante, en el caso de los saltos en el código, es decir, cuando hay una condición o un bucle, el contenido del contador de programa cambiará. También lo hará cuando se haya de ejecutar una interrupción, en ese caso la CPU ejecutará el protocolo para resolverla para volver de nuevo a la ejecución normal.
La dirección de memoria del contador de programa se copiará al registro de acceso a memoria, el cual lo transmitirá al controlador de memoria (externo a la CPU) para que luego el mismo escriba la instrucción deseada en el registro de instrucciones, que será de donde empezará la etapa de captación de instrucciones.
No obstante, no todos los accesos a memoria se realizan para captar datos, también los hay que buscan modificar información, en ese caso el registro en la CPU para comunicarse con el controlador de memoria a través del registro de escritura en memoria, y se aplicará un protocolo distinto que consistirá en modificar la información en la celda de memoria original y en todas sus copias por el sistema.
Memoria caché
(La memoria caché es un punto importante para el funcionamiento de una CPU a dia de hoy, no obstante, le vamos a dedicar su propio artículo en el Glosario).
BRANCH (Predicción de saltos)
Llegamos a uno de esos conceptos que tienen las CPU contemporáneas, pero que no se encontraba en los primeros modelos y que con el paso del tiempo han ido ganando más y más importancia. Nos referimos a las Branch Units o unidades de predicción de saltos. Dichas unidades son necesarias, ya que su trabajo es adivinar la dirección que tomará una CPU en un salto, ya sea por una condición o un bucle. Su importancia se hace capital por el hecho que si el microprocesador central ha de esperar que el salto se resuelva, esto puede llegar a provocar una parada en el sistema, perdiendo ciclos de reloj y rendimiento.
Para ello, las unidades de predicción de ramas/saltos leen por adelantado el código fuente y buscan las instrucciones de salto correspondientes para identificarlas y para evitar las paradas se basan, por lo general, en una base de memorias internas exclusivas de esta unidad que son:
- Branch Target Buffer (BTB): guarda las direcciones de los saltos anteriores que tienen una certeza alta en su ejecución, lo que le permite saber al procesador a cuál de ellas saltar.
- Branch History Table (BHT): Registra el historial de las ramas para predecir si una rama se tomará o no, basándose en el comportamiento del código en el pasado.
Aunque lo más utilizado es la ejecución especulativa, esto consiste en ejecutar todas las ramas posibles por adelantado de forma aislada, para que luego cuando se confirme cuál de ellas es la correcta tener el camino hecho. Por norma se empieza por la rama que ha predicho la unidad de saltos, pero si esta falla se ha de recargar la ruta correcta, es por ello que en predicción especulativa se toman todos los caminos posibles.
DECODE (decodificación de instrucciones)
Independientemente de que nuestra CPU tenga una unidad de predicción de saltos o no, y si estamos ejecutando el código fuente original o una bifurcación condicional del mismo, la siguiente etapa es la decodificación de instrucciones.
Para entender lo que es un decodificador de instrucciones, primero hemos de entender un decodificador a secas, el cual tiene las siguientes características:
- Se trata de un circuito combinacional que convierte una entrada binaria en una salida de varias líneas.
- Si por ejemplo tenemos 2 bits en el extremo de entrada, pasaremos a tener 22= 4 salidas. Si son 3 bits en la entrada, serán entonces 23 = 8 salida, etc.
- Dependiendo de la combinación de 0 y 1 activará una rama u otra y hará pasar dicha señal a través de la misma. Básicamente, recibe una secuencia de bits como entrada y activa una sola línea de salida correspondiente a ese valor de entrada
En un decodificador de instrucciones este las recibe desde el registro de instrucción. Y lo que hace es leer los primeros bits de la misma (opcode) que toma como referencia para ejecutar la rama correspondiente. Al final de cada rama y dependiendo si hablamos de una CPU con ejecución fuera de orden o en orden, nos podemos encontrar con la unidad correspondiente o con el llamado búfer de reordenamiento.
Anatomía de una instrucción
Como punto aparte y para que no os perdáis o vamos a aclarar que es una instrucción. Se trata de una oración escrita en 0 y 1, pero hemos de partir de las siguientes consideraciones:
- Dicha oración carece de sujeto y de tiempos verbales, todo lo ejecuta la CPU de forma imperativa (orden). Los bits iniciales de la instrucción indican la instrucción o la acción que ha de hacer. Puede ser desde una operación aritmética, un salto a otra dirección de memoria, una lectura a memoria, etcétera. A esto lo llamamos opcode.
- El único complemento existente son los directos e indirectos. Estos son los bits que indican el dato, y pueden darse las siguientes condiciones:
- El dato a procesar se encuentra en la misma instrucción.
- Incluye la dirección de memoria donde se encuentra el dato.
- Más complejo aún, incluye la dirección de memoria de la dirección de memoria donde se encuentra el dato.
- No trae dato, normalmente suele apuntar a un registro de memoria de la CPU asociado a dicha instrucción.
El otro tema es la ISA o set de instrucciones, pues bien, este es el idioma de la CPU. Todas leen y escriben binario, pero tú que sabes español y es muy poco probable que sepas finlandés no te vas a entender con un nativo de Finlandia, pese a que usáis el mismo set de símbolos. Pues bien, este es el motivo por el cual no se puede ejecutar un programa para, por ejemplo, x86, en un microprocesador ARM y viceversa.
ISSUE (envío de instrucciones)
La siguiente etapa es producto de lo que llamamos ejecución fuera de orden, donde las instrucciones se reordenan dentro del procesador no por su orden de ejecución, sino por el de disponibilidad de las diferentes unidades de ejecución. De esta manera, estas no terminan paradas sin hacer nada y se consigue sacar más rendimiento.
Antiguamente, la etapa de envío de instrucciones dentro de una CPU formaba parte del decodificador, pero ahora en el otro extremo tiene una compleja maquinaría que no solo clasifica cada instrucción con su unidad de ejecución correspondiente, sino que realiza el ordenamiento correspondiente.
Si la ejecución es dentro de orden en la CPU, entonces todas las instrucciones se ordenaran en orden de ejecución del programa en el Dispatch Búfer para ser enviadas posteriormente a las diferentes unidades cuando estas se encuentren listas.
Ejecución fuera de orden
Cuando las CPU empezaron a poder ejecutar varias instrucciones por primera vez ocurrió un problema, la naturaleza secuencial del código provocaba el desuso de los recursos del microprocesador. Por lo que se llegó a la solución de ejecutar las instrucciones en el orden más adecuado para sacar el mayor provecho en cada momento. A esto se le llama ejecución fuera de orden y es común en todas las CPU contemporáneas.
Para ello, se utilizan los siguientes mecanismos internos en la etapa ISSUE de la CPU:
- Un Scheduler (Programador), el cual controla la asignación de las instrucciones a cada unidad de ejecución. Esta unidad tiene constancia cuando una en concreto queda libre para enviarle una instrucción del tipo que puede ejecutar. En los sistemas más avanzados, incluso tienen la capacidad de predecir el tiempo de ejecución de cada una de ellas para afinar más la asignación.
- Reserva de estaciones: se encarga de almacenar las instrucciones que según el planificador están listas para ejecutarse lo más rápido posible.
- Reorder Buffer (ROB): su trabajo es el de almacenar el orden real de las instrucciones, para que se escriban luego en la etapa WRITE BACK en el orden correspondiente una vez ya han sido resueltas.
EXECUTE(Ejecución de instrucciones)
Llegamos a la penúltima etapa, aquí las unidades correspondientes se encargarán de solventar las diferentes instrucciones en cada una de las unidades correspondientes. El tipo más conocido es lo que llamamos la ALU o unidad aritmético-lógica, pero estas se han ido haciendo más complejas con el tiempo y ahora podemos tener varios tipos de ALU distintas.
- Unidad de enteros escalar: la más común de todas, se encarga de realizar cálculos matemáticos usando números naturales o un rango de números positivos y negativos. Trabaja con un solo dato al mismo tiempo.
- Unidad de coma flotante escalar: la llamada FPU, funciona como la unidad de enteros, pero con números en coma flotante. Es decir, con decimales. Hay una gran cantidad de estándares que indican cuantos bits corresponden al número antes de la coma y cuantos después de la misma.
- SIMD (Unidad vectorial): ejecutan la misma instrucción en conjunto de varios datos al mismo tiempo. Estas unidades pueden ser de longitud variable, y puede ser tanto de coma flotante como de enteros.
- Load/Store: se encarga de las instrucciones de lectura y escritura a memoria. Tienen la capacidad de acceder a los registros de la etapa FETCH para la lectura y escritura a través del controlador de memoria.
- MMU: la CPU tiene una visión particular de la memoria del sistema, ya que lo ve todo bajo un direccionamiento virtual que no corresponde al direccionamiento físico. Es en este punto donde entra la MMU, la cual se encarga de la traducción de direcciones lógicas a direcciones físicas y viceversa, gestiona la memoria virtual y el control del acceso a la memoria. Dicha unidad no suele ser usada por las aplicaciones, pero sí por el sistema operativo.
Registros de propósito general
Los registros de propósito general son pequeñas memorias dentro de la CPU cuya velocidad de acceso es muy rápida y que les sirven a las unidades de ejecución poder realizar sus tareas sin tener que pasar por la RAM principal, lo que acortaría enormemente su rendimiento. Son llamados así por el hecho que en muchas arquitecturas pueden ser usadas por las diferentes unidades.
Dichos registros también reciben el nombre de acumulador, lo que ocurre es que en ciertas arquitecturas el uso de registros no es tan libre y cada uno de ellos tiene un propósito específico, ya sea para un tipo de unidad concreto o para la ejecución de una serie de instrucciones en particular.
FLAGS
Los FLAGS es un microprocesador, son una serie de registros de un solo bit, por lo que suelen unificarse todos y le sirven a la CPU para avisarle de ciertas condiciones. Su activación es automática, por lo que su valor pasa de 0 a 1 cuando ocurre el suceso en particular.
- ZF (Zero Flag): se activa cuando el resultado de una operación aritmética o lógica da cero..
- CF (Carry Flag): Señala si ha habido un acarreo fuera del bit más significativo en una operación aritmética.
- OF (Overflow Flag): Indica si una operación aritmética ha producido un desbordamiento, es decir, si el resultado es demasiado grande o pequeño para caber en el registro asignado.
- SF (Sign Flag): Refleja el signo del resultado de la última operación aritmética. Si el resultado es negativo, este flag se activa.
- IF (Interrupt Flag): Habilita o deshabilita las interrupciones de hardware. Si está activado, la CPU responderá a las interrupciones; si está apagado, las interrupciones son ignoradas temporalmente.
WRITE BACK
Llegamos a la parte final, en ella los datos se escriben en sus memorias correspondientes y se realizan las actualizaciones correspondientes a ello. Por ejemplo, puede ser que en la CPU y la RAM; existan varias copias erróneas o desfasadas de la información recién procesada. También es el momento en que la unidad de instrucción de saltos se actualiza para poder afinar su predicción. Si la CPU tiene una ejecución fuera de orden, lo que hará será utilizar el ROB (Reorder Buffer) para escribir en cada memoria los datos correspondientes en el orden adecuado.
No obstante, si la CPU lo que está ejecutando es una rama en plena predicción especulativa, la etapa de WRITE BACK es algo distinta, ya que no hablamos de la rama principal del programa, sino de una con posibilidades de serlo.
- Se espera que la condición en la rama principal se confirme.
- En el caso de que sea así, se toma la rama especulativa como la principal y el resto de ramas predictivas se paran por completo y sus datos son eliminados del procesador.
- Se apuntan los resultados en la Branch History Table para futuras predicciones de salto.
Con esto terminamos la explicación de cómo funciona una CPU contemporánea en términos generales. Esperamos que os resulte útil y os haya resultado didáctico.
.