RISC-V: diseño y fundamentos de la ISA

Un arquitecto de computadoras será el encargado de diseñar la ISA (Instruction Set Architecture), es decir, definir los tipos de datos que procesará la microarquitectura, los tipos de instrucciones y el listado de las mismas. Decirlo es sencillo, pero estas instrucciones no solo deben ser suficientes para poder ejecutar todas las tareas necesarias para ejecutar todo el software que se usa en la actualidad, sino que deben cumplir una serie de características para que sean eficientes y cumplan unos objetivos.

Para que la ISA sea buena debería diseñarse teniendo en cuenta el coste, simplificad, rendimiento, escalabilidad, tamaño de programa, dependencias y conflictos, implementación (microarquitectura), etc. En el caso de RISC-V, no es una ISA creada para una arquitectura específica, sino que simplemente es una ISA para que otros sean los que diseñen microarquitecturas específicas para poder implementarla.

Las siete métricas

wafer

Antes de diseñar la ISA se debe definir bien estas siete métricas:

Coste:

La ISA debe ser implementada electrónicamente, a no ser que sea un softcore. Mientras más amplio sea el repertorio de instrucciones, y más complejas sean las instrucciones, más complejo será la circuitería necesaria y más superficie ocupará. Mantener un repertorio reducido y de instrucciones simples no solo reducirá el coste de producción del chip, también mejorará otros campos.

Resulta que el coste de producción es muy sensible al tamaño del die o chip. También el rendimiento es sensible a la superficie del die puesto que se pueden crear mayor número de chips por cada oblea, y al ser pequeños, habrá menor probabilidad de que se vean afectados por defectos de fabricación.

Broadwell vs Cortex A72 (tamaño)
Arriba una micrografía de un Intel Broadwell (el recuadro azul es la superficie de un núcleo). Solo el núcleo tiene unas dimensiones de aprox. 8mm2 – Abajo un núcleo ARM Cortex A72 de aprox. 1.15mm2

 

¿Recuerdas esta imagen? Es la comparativa entre un die CISC x86 de Intel y un ARM. Pues bien, la ISA RISC-V es aún más minimalista que ARM, lo que supone un menor coste. Por poner un ejemplo, un ARM Cortex-A6 con 16 KB de cache y un Rocket de lowRISC (RISC-V) con 16 KB, y usando el mismo proceso de fabricación, tienen un tamaño de 0.53mm2 y 0.27mm2 respectivamente. Eso es casi el doble de tamaño para ARM lo que se traduce en 4 veces más caro…

wafer Yield
Mientras mayor sea el diámetro de la oblea o menor sea la superficie del dado (o ambos a la vez), más cantidad de chips por oblea y mayor cantidad de chips funcionales saldrán. Los puntos representan los posibles fallos que se pueden generar durante la fabricación. En el wafer con solo 4 dices solo saldría un chip funcional. En la oblea con 9 dados tendrías 5 funcionales. A la derecha se puede apreciar cómo en los chips multinúcleo se complica aún más la situación. Solo habría un dado completo funcional con 4 núcleos. Pero se podrían remarcar el resto y aprovecharlos como tri-core en dos casos y dualcore en uno de ellos… O si no ¿por qué te crees que Intel y AMD han rescatado las marcas Pentium, Celeron y Athlon? ¿Qué crees que son?

 

Por eso algunos como AMD están optando por chiplets para abaratar costes de producción y aumentar el rendimiento de cada wafer. Con la llegada de los multinúcleo el yield ha bajado considerablemente, y mientras más núcleos tiene el die peor aún. En definitiva, mayor superficie de silicio, mayor probabilidad de que estén afectados por algún problema… Pero, al no ser chips monolíticos, la velocidad de interconexión entre ellos es menor.

Con RISC-V no solo tendrías un coste de producción menor, sino que podrías implementar mayor cantidad de núcleos por die sin afectar tanto a ese rendimiento debido a su reducido tamaño. Eso significa una mayor escalabilidad y conseguir chips con mayor rendimiento y bajo consumo.

El proceso de fabricación influye mucho en los costes de producción, también el nodo de la litografía usado y el diámetro del wafer. Por cierto, al coste de fabricación hay que añadir el de diseño que pasa por sumar los costes personales, los costes de las herramientas empleadas (software, equipos,…), costes indirectos, etc.

Todas estas fórmulas y cálculos (ROI, yield, costes por CI,…) los detallo en mi enciclopedia El Mundo de Bitman Volumen III.

Simplicidad:

En el apartado anterior ya has visto la relación simplicidad y costes. Pero buscar una ISA simple también hace que sea más fácil de diseñar y validar, reduciendo el tiempo de pruebas y diseño de las microarquitecturas. Si analizas otras ISAs, como x86, hay instrucciones muy estúpidas como enter o leave. Esa instrucción debe ser la primera en ejecutarse para crear el stack frame. Pero la mayor parte de los compiladores usan lo siguiente como sustituto:

; Equivalente a la instrucción enter

push ebp
mov ebp, esp
sub esp, imm

; Equivalencia a la instrucción leave

mov esp, ebp
pop ebp

Siempre se tiende a usar instrucciones más simples aunque eso implique usar más… En este caso, los modernos microprocesadores necesitan unas 10 o 20 microoperaciones para traducir esa instrucción enter tan completa, además de tener una alta latencia de unos 8 ciclos de reloj en algunos casos. En cambio, para procesar una de las otras tan solo es necesario entre 4 y 6 microops dependiendo de la microarquitectura, con aproximadamente 3 ciclos de reloj de latencia para las tres instrucciones equivalentes. ¿Entiendes ahora el motivo de simplificar?

Rendimiento:

Siempre se necesita sacar el mayor rendimiento posible de un chip, a excepción de algunos casos donde se sacrifica rendimiento para mejorar la eficiencia energética para ciertas aplicaciones móviles y embebidas. Como ya especifiqué en mi curso de supercomputación, el rendimiento básicamente a la hora de ejecutar un programa básicamente se ve afectado por la cantidad de instrucciones que contiene un programa, el CPI (Cycles Per Instruction), y la frecuencia de reloj (tiempo de cada ciclo).

Esto puede llegar a pensar que si se usan instrucciones más complejas de tipo CISC se necesitarán menos instrucciones para ejecutar un mismo programa, y por tanto se ejecutará más rápido. Pero no es así. Las instrucciones simples se procesan de forma más rápida, en menos cantidad de ciclos de reloj y los RISC tienden a ser más rápidos. Por otro lado, al ser chips más pequeños se reduce el camino crítico y se puede aumentar la profundidad de la pipeline y la frecuencia de reloj.

Por tanto, en un RISC estamos mejorando dos de los tres factores que influyen en el rendimiento, mientras que en el CISC solo mejoras uno.

Por ejemplo, imagina una CPU A con una frecuencia de reloj de 1 Hz (T=1s) que es capaz de procesar 1 instrucción por cada ciclo de reloj (CPI=1), y otra CPU B que tiene 2 Hz (T=0.5s) y es capaz de procesar una instrucción y un tercio (aprox CPI=1.33) en cada ciclo. Si ejecutamos un mismo programa con 500 instrucciones en ambos casos, los resultados serían:

500 · 1 · 1 = 500 segundos (CPU A)

500 · 1.33 · 0.5 = 332,5 segundos (CPU B)

En próximos artículos introduciremos el speedup, Ley de Gustafson, diferencias entre CPI, IPS, IPC,… pero por el momento simplemente quiero que captes la idea básica.

Imagina otro caso de una CPU C de tipo CISC, en este caso, se supone que habrá menos instrucciones que ejecutar para ese mismo programa, pero también tendremos un CPI y una frecuencia menor. Por ejemplo, suponiendo que tiene la misma frecuencia que A y un promedio de instrucciones por ciclo de 4:

125 · 4 · 1 = 500 segundos (CPU C)

Y digo promedio, puesto que no todas las instrucciones se suelen ejecutar en la misma cantidad de ciclos. Algunas suelen tardar más y otras menos, aunque en un RISC suelen ser bastante parejas. Pero puedes encontrar una CPU CISC donde las instrucciones de coma flotante necesiten de 3 ciclos de reloj, enteros 2, las de salto 4, etc.

Esto también te lleva a pensar, que dependiendo del tipo de software a ejecutar, podría variar el rendimiento de una microarquitectura a otra. Por ejemplo, un microprocesador diseñado para supercomputación, donde se ejecutarán apps científicas, se debe optimizar para las instrucciones de coma flotante, que serán las más frecuentes… Más adelante dedicaré un artículo a métricas y fórmulas de rendimiento. Por ahora con esto me basta para lo que intento explicar.

Aislamiento de arquitectura e implementación:

Desde los años 1960s se hace esta distinción entre la arquitectura en sí y su implementación. Debes tener en cuenta que el programador a bajo nivel, es decir, el que emplea lenguaje ensamblador, debe conocer la arquitectura para poder crear programas. También los desarrolladores de compiladores tiene que tener conocimientos profundos para que el compilador genere un código lo más optimizado posible.

Si no lo sabes ya, más adelante comprenderás que el compilador tiene mucho que hacer a la hora de extraer más rendimiento re-ordenando las instrucciones de una forma adecuada. Me estoy refiriendo a las técnicas de optimización como las ventanas de relleno, eliminación de redundancias, la multiprogramación y multihilo, optimizaciones para los bucles para una determinada microarquitectura, code ilining, etc.

Por otro lado, los arquitectos o diseñadores de la ISA deben crear un repertorio de instrucciones lo más optimizado posible para cualquier implementación (microarquitectura). No es práctico crear instrucciones para beneficiar a una microarquitectura concreta o perjudicar a otra…

Espacio para crecer:

Con la Ley de Moore muriendo, una de las prácticas para aumentar el rendimiento de forma significativa pasa por extender la ISA con instrucciones específicas para un campo determinado. Por ejemplo, en las actuales ISAs se pueden agregar instrucciones para aplicaciones de IA, para multimedia, realidad virtual/aumentada/mixta, operaciones vectoriales, etc. Es decir, deben ser flexibles para adaptarse a las nuevas necesidades del software actual.

Tamaño del programa:

Ya has visto en los anteriores apartados el impacto del tamaño del programa en el rendimiento. Mientras más pequeño es el programa mejor. Además, una menor cantidad de instrucciones a ejecutar también implica menos posibilidades de fallos de cache al intentar acceder a ella. Por otro lado, cuando hay un fallo de cache, habrá que ir a buscar el dato a la RAM y la memoria principal tiene un consumo superior, es decir, también consumirá más energía.

Evidentemente, reducir el tamaño del programa pasa por aumentar la complejidad de las instrucciones, algo en contra de la filosofía RISC. Así que los arquitectos deben buscar un buen compromiso entre tamaño y complejidad. A esto hay que agregar que no siempre una complejidad o longitud mayor de una instrucción implica reducir su número si la ISA es una porquería.

Un ejemplo de esto es la x86-32 (IA-32) de Intel, donde las instrucciones van de 1 Byte a 15 bytes de longitud frente a los 32 bits constantes de las instrucciones ARM, RISC-V, etc. Podrías pensar que la CISC x86 tiene un tamaño de programa relativo mucho menor, pero resulta que en RISC-V es tan solo un 9% superior y en el caso de ARMv7 es de solo un 6% mayor. Pero cuando se usan extensiones como RV32C y Thumb-2 para RISC-V y ARM respectivamente, estos sets de instrucciones compresas dan como resultado un tamaño de programa en torno al 26% menor que en x86-32.

Facilidad de programar, compilar y enlazar:

Que el compilador sepa hacer un buen uso de los registros es determinante para extraer más rendimiento. Ir a buscarlo a otras memorias de niveles superiores resulta más costoso en términos de tiempo (latencia), y consumo energético. Además, mientras más registros tenga el programador disponibles, mucho mejor. En el caso de RISC-V se tienen 32 registros para enteros. En el caso de la ISA AArch32 (ARM-32) es de 16 y aún peor en el caso de IA-32 que es de 8.

No tener una arquitectura demasiado dispar en cuanto a CPI es también determinante para facilitarle la vida a los programadores. Cuando tienes una ISA CISC, o algunas RISC, con instrucciones que tardan diferente cantidad de ciclos de reloj en ejecutarse, eso resulta más tedioso para optimizar el código. Eso sin contar los fallos de cache que llevan a desperdiciar varios ciclos para el acceso del dato o instrucción en la memoria principal…

RISC-V también simplifica eso, ya que sin tener en cuenta los fallos de cache, cada instrucción tarda lo mismo en ejecutarse: 1 ciclo de reloj. En cambio, en IA-32 van desde uno a varios ciclos, incluso cuando no hay fallos de cache. Igual ocurre para ARM, aunque no es tan escandaloso como en el caso de la ISA de Intel. Y si echamos un vistazo a los modos de acceso presentes en x86, que incluso permite operaciones aritméticas con operandos cargados directamente en la memoria principal en vez de solo en los registros. ¡OMG!

¿Recuerdas que ya comenté en el primer artículo de esta serie que x86 es una auténtica porquería para aprender ensamblador? Pues eso…

Y podríamos seguir sumando otras tantas trastadas y chapuzas de la x86. ¿Sabéis? Tengo la sensación de que los nuevos microprocesadores basados en RISC-V que están creando en Europa (EPI), China (¿?), Rusia (sin demasiada info ¿posiblemente el mismo grupo de Elbrus?) e India (Shatki de RISE-IIT), pueden ser un auténtico grano problema para Intel y AMD en un futuro no muy lejano…

Isaac

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

Deja un comentario

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