¿Por qué la mayoría de los RISC ISA no escriben enteros MULH/MUL o DIV/REM en dos registros de propósito general? [cerrado]

La mayoría de los algoritmos de multiplicación y división de hardware pueden calcular las palabras altas y bajas de un producto de dos enteros, o tanto el cociente como el resto de la división de dos enteros, al mismo tiempo. En las principales RISC ISA, existen muchos enfoques diferentes para la multiplicación y división de enteros. (En esta publicación, solo estoy considerando números enteros y no matemáticas de punto flotante).

  • La mayoría de los primeros diseños de RISC no tienen tales instrucciones.
  • MIPS I tiene instrucciones de multiplicación que devuelven el resultado de la palabra doble en un par de registros especiales, $HI:$LO, así como instrucciones de división que devuelven el cociente en $LO y el resto en $HI. Mover el contenido de $HI o $LO a un registro de propósito general es otra instrucción.
  • SPARC V8 y superior almacenará la palabra baja de una multiplicación o el cociente de una división en un registro específico, pero almacena la palabra alta de la multiplicación o el resto de la división en un registro especial, %y.
  • La mayoría de las arquitecturas RISC de los años 90, incluidas POWER, Alpha y PA-RISC, tienen instrucciones separadas para poner la palabra alta o la palabra baja de una multiplicación en cualquier registro de uso general y para poner un cociente en cualquier registro general. registro de propósito, pero no para calcular un resto en absoluto; para obtener el resto, calcularía c = a / b , luego encontraría d = a - b × c .
  • El ARM A32 ISA tiene dos de las instrucciones que me interesan SMULLy UMULL, que se introdujeron en v3M. Estos almacenan la palabra alta y baja del producto de dos enteros de 32 bits en diferentes registros de 32 bits. También tiene muchas otras instrucciones de multiplicación de variantes. Sin embargo, no calcula el resto de una división.
  • ARM A64 redefine SMULLy UMULLalmacena el producto de 64 bits de dos enteros de 32 bits en un registro de 64 bits. No tiene instrucción de multiplicación de 64 bits per se . Para multiplicar, haces una multiplicación-suma que suma el registro cero. Hay división de enteros, pero no módulo de enteros.
  • El RISC-V ISA con la extensión M tiene varias variantes de MULH, y , pero el Manual del conjunto de instrucciones de RISC-VMUL recomienda que cuando a va seguido de a con los mismos operandos de origen, o de a con los mismos operandos de origen, las microarquitecturas puede fusionar estas operaciones en una sola operación en lugar de realizar dos por separado.DIVREMMULHMULDIVREM

Definitivamente es posible calcular el resultado completo de una multiplicación o división y el resto al mismo tiempo. ¡Varios RISC ISA tienen una instrucción para hacerlo! En teoría, si solo desea un resultado, puede establecer el destino del otro en el registro cero. ¿Por qué, entonces, ningún RISC ISA que miré tiene una instrucción para almacenar tanto el cociente como el resto en registros separados de propósito general, y por qué el ISA que encontré hace esto para las palabras altas y bajas de un producto? ARM A32, ¿dejarlo caer en la próxima revisión importante?

Estoy particularmente interesado en saber por qué los primeros chips RISC de mediados de los 80 eligieron este diseño. SPARC V8 no tenía un formato de instrucción con dos registros de origen y dos de destino y posiblemente no quería complicar su decodificador con otro formato. MIPS I: ¿cortar esquinas para adaptar instrucciones más complejas a una canalización RISC clásica?

Pero también me pregunto por qué las arquitecturas RISC modernas se han alejado de él. Supongo que ARM A64 lo hace de esa manera porque la suma y multiplicación de precisión fija es útil para la decodificación multimedia, con menos sobrecarga relativa cuanto más bits multiplique y, una vez que tenga eso, tiene sentido reutilizar los circuitos para la multiplicación ( solo agregue 0). Pero la documentación de RISC-V sugiere que el núcleo debe tener una sola operación que calcule ambos resultados, entonces, ¿por qué no, en un diseño de RISC, exponer eso en el ISA?

¿Se han publicado artículos que traten este tema? ¿O los diseñadores de estas arquitecturas han explicado alguna vez su razón de ser?

¿Por qué? ¿Por qué no? El hecho de que algo sea posible no significa que sea deseable desde cualquier punto de vista, económico, cultural, área de matriz, SPARC lo intentó, así que vamos a hacer algo diferente, a nuestro compilador no le gustaría, nuestro compilador. eso sugirió que acaba de irse, así que no lo hagamos ... ¿Sabes lo complicada que se vuelve la política de la oficina cuando necesitas tomar una decisión más o menos arbitraria de cara al cliente?
¿Fue tan arbitrario, entonces? ¿O había una razón particular por la que era inviable? ¿O no es una optimización útil?
Muy buena pregunta, en realidad. Puede buscar en la serie adsp-2100 su instrucción especial div. Eso podría usarse, pero solo era bueno para un bit a la vez. He escrito muchas rutinas para realizar divisiones para FP, así como el uso de enteros, y siempre puedo proporcionar ambas tan fácilmente como solo una de ellas. Mismo tiempo, mismos ciclos. Así que sí, una buena pregunta.
Solo como nota al margen, ya que parece que has estado investigando. Décadas atrás, Bipolar Integrated Technologies desarrolló el primer IC de división FP completamente combinatorio. Se han ido hace mucho ahora.
@jonk Ah. El precursor de RISC, el IBM 801, también tenía una instrucción de paso dividido. mirrorservice.org/sites/www.bitsavers.org/pdf/ibm/system801/… (42)
No estaba diciendo nada novedoso sobre el adsp-2100. Solo llamando la atencion. Veré los documentos que mencionas cuando tenga un momento y veré cómo se compara. Sin embargo, creo que la división BIT FP fue la primera en su escala. Y no reubicado desde entonces, tal vez. Sin embargo, estaría interesado en estar equivocado.
Solo pensamientos... La multiplicación y la división usan una lógica muy profunda en comparación con otras operaciones de la CPU y eso las convierte a menudo en las más lentas de implementar. Entonces puedo razonar por qué un diseño de CPU desacopla su MUL y DIV del núcleo, para aumentar la velocidad del resto de la ejecución de instrucciones. Me hace preguntarme si los restos provienen de una etapa lógica más profunda que el resultado DIV. Dado que se puede calcular con bastante facilidad a partir del resultado DIV, podría tener sentido. No sé si eso fue obvio para ti de todos modos o si ayuda :-)
claramente vale la pena cerrar esto debido principalmente a la opinión. No hay una respuesta real que tendría que buscar cada uno de los equipos de diseño para cada arquitectura. Tal vez como se mencionó, usted entiende 1) tiene MUCHA suerte de tener una división de hardware si tiene una 2) lo mismo para multiplicar, pero no es tan doloroso y se implementa con más frecuencia que dividir. para muchas operaciones de ciclo de reloj, requieren cantidades mínimas de lógica, pero a medida que se acerca al ciclo de reloj único, pueden comenzar a inundar todo el resto del diseño.
es por eso que ve en algunos núcleos de brazo, por ejemplo, una opción para usar una multiplicación de un solo ciclo o una multiplicación de muchos ciclos. ahorre en bienes raíces de chips. no estoy seguro sobre el resto de su pregunta, es obvio que una multiplicación de Nbits = Nbits * Nbits es algo inútil y derrochadora, ahora un Nbit = Nbit/Nbit es más útil. Si su queja es que no puede elegir bien todos los registros, eso es una lástima, es probable que haya razones obvias para eso, pero es probable que sean específicos de la arquitectura. y si está en una solución de operación de un solo reloj, entonces quizás calcular el módulo por separado tenga una compensación válida.
@old_timer Si alguien se pregunta por qué esta pregunta vuelve a llamar la atención de repente, siete meses después de que se cerró originalmente, es porque otro usuario la editó para agregar "una pregunta clara de llamado a las armas". Y luego comenzó a recibir más comentarios y votos. Así que pensé, también podría ampliar eso.

Respuestas (1)

Por lo general, los requisitos comerciales para los diseños de procesadores están dirigidos a cumplir con los puntos de referencia sintéticos o las cargas de trabajo de aplicaciones del mundo real . Las características que no abordan ninguno de los dos son más difíciles de vender y, por lo tanto, es más probable que se dejen de lado.

Durante los años 80, el benchmark Dhrystone fue muy popular. Por lo general, esto se presentaría en un lenguaje de alto nivel, generalmente C. Dhrystone no incluye ninguna operación de resto. Por lo tanto, los diseñadores que busquen una puntuación Dhrystone alta podrían dejar de lado la operación restante para reducir unos picosegundos del tiempo del ciclo.

La mayoría de los lenguajes de alto nivel tienen operadores aritméticos separados para el cociente y el resto, muy pocos proporcionan operaciones estandarizadas para obtener ambos de una sola operación (¡en parte porque C y FORTRAN no tienen tuplas nativas!). Hasta hace poco, los compiladores no han sido buenos en el tipo de detección de optimización que les permitiría unir dos operaciones en una sola instrucción.

Si observamos el tipo de trabajo aritmético pesado para el que se han optimizado los procesadores, tiende a no preocuparse por los restos. Los grandes ejemplos son FFT y multiplicación de matrices para álgebra lineal. Esa es la razón por la cual los procesadores tienden a tener instrucciones de acumulación múltiple e instrucciones SIMD.

¡Gracias! En parte me motivó resolver un problema con un algoritmo rápido que involucraba muchas %operaciones. La biblioteca estándar de C tiene div()y ldiv(), que devuelven estructuras de dos miembros y están destinadas a aprovechar el hardware que encuentra tanto el cociente como el resto a la vez. Los compiladores ahora generalmente pueden hacer esta optimización automáticamente, esas instrucciones son (como se señaló) menos comunes y /han %sido tan bien especificadas como div()y ldiv()desde C99. Realmente ya no hay una ventaja en usarlos, y me pregunto cuánto código hizo alguna vez.
Ya que mencionas FFT, eso plantea la pregunta: ¿el algoritmo FFT de factor primo Good-Thomas era relativamente impopular porque era modlento y era modlento porque los algoritmos como Good-Thomas no se consideraban importantes?
div/mod siempre ha sido más lento que la multiplicación, lo que creo que debe haber impulsado esa elección. Muchas arquitecturas tienen una multiplicación de un solo ciclo y una división más lenta; algunos de los microcontroladores se han multiplicado pero no se han dividido en absoluto.
En el lado de la multiplicación, se nota que hasta que C99 agregó el tipo "largo largo" no había forma de escribir una multiplicación de 32x32->64 en un sistema ILP32 de 32 bits en C estándar y todavía no hay forma de escribir una multiplicación de 64x64- >128 multiplicar en estándar C.