Cómo se diseñan y construyen las CPU

Todos consideramos la CPU como un «cerebro» de una computadora, pero ¿qué significa realmente? ¿Qué está pasando con miles de millones de transistores para que su computadora funcione? En esta miniserie de cuatro partes, nos centraremos en el diseño del {hardware} de la computadora que cubre los entresijos de lo que hace que una computadora funcione.

La serie cubrirá la arquitectura de la computadora, el diseño del circuito del procesador, la integración VLSI (muy gran escala), la fabricación de chips y las tendencias futuras en informática. Si siempre ha estado interesado en los detalles de cómo funcionan los procesadores en el inside, quédese porque eso es lo que quiere saber para comenzar.

Comenzamos en un nivel muy alto de lo que hace un procesador y cómo los bloques de construcción se unen en un diseño funcional. Esto incluye núcleos de procesador, jerarquía de memoria, predicción de ramificación y más. Primero, necesitamos una definición básica de lo que hace una CPU. La explicación más easy es que una CPU sigue un conjunto de instrucciones para realizar alguna operación en un conjunto de entradas. Por ejemplo, esto podría ser leer un valor de la memoria, luego agregarlo a otro valor y eventualmente guardar el resultado en la memoria en otro lugar. También puede ser algo más complejo que dividir dos números si el resultado del cálculo anterior fue mayor que cero.

Cuando desea ejecutar un programa como un sistema operativo o un juego, el programa en sí es una serie de instrucciones para que la CPU las ejecute. Estas instrucciones se cargan desde la memoria y en un procesador easy, se ejecutan una por una hasta que se completa el programa. Mientras que los desarrolladores de software program escriben sus programas en lenguajes de alto nivel como C ++ o Python, por ejemplo, el procesador no puede entenderlo. Solo comprende los 1 y los 0, por lo que necesitamos una forma de representar el código en este formato.

Los programas se ensamblan en un conjunto de instrucciones de capa llamadas lenguaje ensamblador como parte de una arquitectura de conjunto de instrucciones (ISA). Este es el conjunto de instrucciones que la CPU está diseñada para comprender y ejecutar. Algunos de los ISA más comunes son x86, MIPS, ARM, RISC-V y PowerPC. Así como la sintaxis para escribir una función en C ++ es diferente de una función que hace lo mismo en Python, cada ISA tiene una sintaxis diferente.

Estas NIA se pueden dividir en dos categorías principales: longitud fija y longitud variable. El RISC-V ISA utiliza instrucciones de longitud fija, lo que significa que un cierto número predefinido de bits en cada instrucción determina qué tipo de instrucción es. Esto difiere de x86, que utiliza instrucciones de longitud variable. En x86, las instrucciones se pueden codificar de diferentes maneras y con diferentes números de bits para diferentes partes. Debido a esta complejidad, el decodificador de instrucciones de CPU x86 suele ser la parte más compleja de todo el diseño.

Las instrucciones de longitud fija permiten una decodificación más fácil debido a su estructura common, pero limitan el número de instrucciones generales que puede admitir un ISA. Mientras que las versiones regulares de la arquitectura RISC-V tienen alrededor de 100 instrucciones y son de código abierto, x86 es propietario y nadie sabe realmente cuántas instrucciones hay. La gente generalmente cree que hay unos pocos miles de instrucciones x86, pero el número exacto no es público. A pesar de las diferencias entre los ISA, tienen esencialmente la misma funcionalidad central.

Ahora estamos listos para encender nuestra computadora y comenzar a ejecutar cosas. Ejecutar una instrucción en realidad tiene varias partes básicas que se dividen a través de los muchos pasos de un procesador.

El primer paso es recuperar las instrucciones de la memoria en la CPU para comenzar la ejecución. En el segundo paso, la instrucción se decodifica para que la CPU pueda determinar qué tipo de instrucción es. Existen muchos tipos, incluidas las instrucciones aritméticas, las instrucciones de bifurcación y las instrucciones de memoria. Cuando la CPU sabe qué tipo de instrucción está ejecutando, los operandos para la instrucción se recopilan de la memoria o registros internos en la CPU. Si desea agregar el número A al número B, no puede hacer la suma hasta que realmente conozca los valores de A y B. La mayoría de los procesadores modernos son de 64 bits, lo que significa que el tamaño de cada valor de datos es de 64 bits.

Cuando la CPU tiene los operandos para la instrucción, se mueve al paso de ejecución donde se realiza la operación en la entrada. Esto puede sumar los números, realizar una manipulación lógica de los números o simplemente pasar los números sin cambiarlos. Una vez que se calcula el resultado, puede ser necesario acceder a la memoria para guardar el resultado; de lo contrario, la CPU puede contener el valor en uno de sus registros internos. Una vez que se guarda el resultado, la CPU actualizará el estado de varios elementos y pasará a la siguiente instrucción.

Por supuesto, esta descripción es una gran simplificación, y la mayoría de los procesadores modernos dividirán estos pocos pasos en 20 o más fases más pequeñas para mejorar la eficiencia. Esto significa que incluso si el procesador inicia y finaliza múltiples instrucciones en cada ciclo, puede tomar 20 o más ciclos para que una sola instrucción se full de principio a fin. Este modelo generalmente se llama tubería, ya que lleva un tiempo llenar la tubería y que el fluido pase a través de ella por completo, pero una vez que está llena, obtienes una salida constante.

Todo el ciclo por el que pasa una instrucción es un proceso muy coreografiado, pero no todas las instrucciones pueden terminar al mismo tiempo. Por ejemplo, la adición es muy rápida, mientras que dividir o cargar desde la memoria puede llevar cientos de ciclos. En lugar de detener todo el procesador mientras se completa una instrucción lenta, la mayoría de los procesadores modernos se ejecutan fuera de orden. Esto significa que decidirán qué instrucción sería la más ventajosa de ejecutar en un momento dado y mejorarán otras instrucciones que no estén listas. Si la instrucción precise aún no está lista, el procesador puede saltar al código para ver si hay algo más listo.

Además de la ejecución fuera de orden, los procesadores modernos típicos usan lo que se llama una arquitectura superescalar . Esto significa que en cualquier momento dado, el procesador ejecuta muchas instrucciones a la vez en cada paso de la tubería. También puede esperar a cientos para comenzar la ejecución. Para poder ejecutar muchas instrucciones a la vez, los procesadores tendrán múltiples copias de cada paso de tubería dentro. Si un procesador ve que dos instrucciones están listas para ejecutarse y no hay dependencia entre ellas, en lugar de esperar a que finalicen por separado, las ejecutan ambas al mismo tiempo. Una implementación común de esto se llama Simith Multithreading (SMT), también conocido como Hyper-Threading. Los procesadores Intel y AMD actualmente admiten SMT de dos vías, mientras que IBM ha desarrollado chips que admiten SMT de hasta ocho vías.

Para realizar este rendimiento cuidadosamente coreografiado, un procesador tiene muchos elementos adicionales más allá del núcleo básico. Hay cientos de módulos individuales en un procesador, cada uno con un propósito específico, pero solo veremos los conceptos básicos. Los dos más grandes y más ventajosos son el caché y el predictor de rama. Las estructuras adicionales que no cubriremos incluyen cosas como el reordenamiento del búfer, el registro de la tabla de alias y las estaciones de reserva.

El propósito de las memorias caché a menudo puede ser confuso, ya que almacenan datos como RAM o un SSD. Sin embargo, lo que distingue a los cachés es su retraso y velocidad de acceso. Aunque la RAM es extremadamente rápida, los pedidos de tamaño son demasiado lentos para una CPU. La RAM puede tardar cientos de ciclos en responder con datos y el procesador se quedaría estancado sin nada que hacer. Si los datos no están en la RAM, puede tomar decenas de miles de ciclos acceder a los datos en un SSD. Sin cachés, nuestros procesadores se detendrían.

Los procesadores suelen tener tres niveles de caché que forman lo que se conoce como una jerarquía de memoria . El caché L1 es el más pequeño y rápido, el L2 está en el medio y el L3 es el más grande y más lento del caché. Por encima del caché de la jerarquía hay pequeños registros que almacenan un único valor de datos bajo cálculo. Estos registros son las unidades de almacenamiento más rápidas en su sistema por orden de tamaño. Cuando un compilador transforma un programa de alto nivel en lenguaje ensamblador, determina la mejor manera de usar estos registros.

Cuando la CPU solicita datos de memoria, primero verificará si estos datos ya están almacenados en el caché L1. . Si es así, se puede acceder a los datos rápidamente en solo unos pocos ciclos. Si no está presente, la CPU verificará L2 y luego buscará en el caché L3. Las cachés se implementan de tal manera que generalmente son transparentes para el núcleo. El kernel solo pedirá algunos datos en una dirección de memoria especificada y qué nivel en la jerarquía que tiene responderá. A medida que avanzamos a las fases posteriores en la jerarquía de memoria, el tamaño y la latencia generalmente aumentan con los pedidos de tamaño. En última instancia, si la CPU no puede encontrar los datos que está buscando en ninguno de los caché, primero irá a la memoria principal (RAM).

En un procesador típico, cada núcleo tiene dos cachés L1: uno para datos y otro para instrucciones. El caché L1 suele ser de unos 100 kilobytes en whole, y el tamaño puede variar según el chip y la generación. También suele haber un caché L2 para cada núcleo, aunque en algunas arquitecturas se puede compartir entre dos núcleos. El caché L2 suele ser de unos cientos de kilobytes. Finalmente, hay un único caché L3 que se comparte entre todos los núcleos y está en el orden de docenas de megabytes.

Cuando un procesador ejecuta código, las instrucciones y los valores de datos que usa con frecuencia se almacenan en caché. Esto acelera significativamente la ejecución ya que el procesador no tiene que acceder constantemente a la memoria principal para obtener los datos que necesita. Hablaremos más sobre cómo se implementan estos sistemas de memoria en la segunda y tercera entrega de esta serie.

Además de los cachés, uno de los otros bloques de construcción centrales de un procesador moderno es un predictor de rama exacto . Las instrucciones de bifurcación son similares a las declaraciones «if» para un procesador. Se ejecuta un conjunto de instrucciones si la condición es verdadera y otra se ejecuta si la condición es falsa. Por ejemplo. Puede comparar dos números y, si son iguales, realizar una función y, si son diferentes, realizar otra función. Estas instrucciones de ramificación son extremadamente comunes y pueden representar aproximadamente el 20% de todas las instrucciones en un programa.

En la superficie, estas instrucciones de ramificación pueden no parecer un problema, pero en realidad pueden ser muy desafiantes para que un procesador funcione correctamente. Dado que la CPU puede realizar diez o veinte instrucciones a la vez, es muy importante saber qué instrucciones ejecutar. Puede tomar 5 ciclos para determinar si la instrucción precise es una rama, y ??otros 10 ciclos para determinar si la condición es verdadera. En ese punto, el procesador puede haber comenzado a realizar docenas de instrucciones adicionales sin siquiera saber si eran las instrucciones correctas para ejecutar.

Para solucionar este problema, todos los procesadores modernos de alto rendimiento emplean una técnica llamada especulación. Lo que esto significa es que el procesador hará un seguimiento de las instrucciones de la rama y adivinará si se tomará la rama o no. Si la predicción es correcta, el procesador ya ha comenzado a ejecutar instrucciones posteriores para que esto proporcione una ganancia de rendimiento. Si la predicción es incorrecta, el procesador detiene la ejecución, elimina todas las instrucciones incorrectas que ha comenzado a ejecutar y se reinicia desde el punto correcto.

Estos predictores de rama son algunas de las primeras formas de aprendizaje automático, ya que el predictor aprende el comportamiento de las ramas a medida que avanza. Si predice mal muchas veces, comenzará a aprender el comportamiento correcto. Décadas de investigación en técnicas de pronóstico de la industria han dado como resultado precisiones superiores al 90% en procesadores modernos.

Si bien la especulación ofrece enormes ganancias de rendimiento ya que el procesador puede ejecutar instrucciones que están listas en lugar de esperar en línea con las ocupadas, también expone vulnerabilidades de seguridad. El famoso ataque Spectre explota errores en la predicción y especulación de ramas. El atacante usará código especialmente diseñado para hacer que el procesador ejecute especulativamente código que pierde valores de memoria. Algunos aspectos de la especulación han tenido que ser rediseñados para garantizar que los datos no se puedan filtrar y esto resultó en una ligera disminución en el rendimiento.

La arquitectura utilizada en los procesadores modernos ha recorrido un largo camino en las últimas décadas. Las innovaciones y el diseño inteligente han dado como resultado un mayor rendimiento y una mejor utilización del {hardware} subyacente. Sin embargo, los fabricantes de CPU son muy reservados acerca de las tecnologías en sus procesadores, por lo que es imposible saber exactamente qué está sucediendo dentro. Dicho esto, los estándares de cómo funcionan las computadoras están estandarizados en todos los procesadores. Intel puede estar agregando su salsa secreta para aumentar las tasas de aciertos de caché, o AMD puede estar agregando un predictor de rama avanzado, pero ambos están realizando la misma tarea.

Este primer vistazo y una descripción common cubrieron la mayoría de los conceptos básicos sobre cómo funcionan los procesadores. En la segunda parte, discutimos cómo se diseñan los componentes que entran en la CPU, cubrimos las puertas lógicas, el reloj, la administración de energía, los diagramas de circuitos y más.

Advertisement