Warum schreiben die meisten RISC-ISAs nicht ganzzahlige MULH/MUL oder DIV/REM in zwei Mehrzweckregister? [geschlossen]

Die meisten Hardware-Multiplikations- und Divisionsalgorithmen können die hohen und niedrigen Wörter eines Produkts aus zwei ganzen Zahlen oder sowohl den Quotienten als auch den Rest der Division zweier ganzer Zahlen gleichzeitig berechnen. In großen RISC-ISAs gibt es viele verschiedene Ansätze zur ganzzahligen Multiplikation und Division. (In diesem Beitrag betrachte ich nur Integer- und keine Fließkomma-Mathematik.)

  • Die meisten frühen RISC-Designs haben solche Anweisungen überhaupt nicht.
  • MIPS I verfügt über Multiplikationsbefehle, die das Doppelwortergebnis in einem Paar von Spezialregistern, $HI:$LO, zurückgeben, sowie über Divisionsbefehle, die den Quotienten in $LO und den Rest in $HI zurückgeben. Das Verschieben des Inhalts von entweder $HI oder $LO in ein Mehrzweckregister ist eine weitere Anweisung.
  • SPARC V8 und höher speichert das untere Wort einer Multiplikation oder den Quotienten einer Division in einem bestimmten Register, aber speichert das obere Wort der Multiplikation oder den Rest der Division in einem Spezialregister, %y.
  • Die meisten RISC-Architekturen der 90er Jahre, einschließlich POWER, Alpha und PA-RISC, haben separate Anweisungen, um entweder das High-Word oder das Low-Word einer Multiplikation in ein beliebiges Mehrzweckregister und einen Quotienten in ein beliebiges allgemeines Register zu stellen. Zweck registrieren, aber überhaupt keinen Rest berechnen; Um den Rest zu erhalten, würden Sie c = a / b berechnen und dann d = a - b × c finden .
  • Der ARM A32 ISA hat zwei der Anweisungen, die mich interessieren, SMULLund UMULL, die in v3M eingeführt wurden. Diese speichern das hohe und das niedrige Wort des Produkts zweier 32-Bit-Ganzzahlen in verschiedenen 32-Bit-Registern. Es hat auch viele andere Varianten von Multiplikationsanweisungen. Es berechnet jedoch nicht den Rest einer Division.
  • ARM A64 definiert neu SMULLund UMULLspeichert das 64-Bit-Produkt zweier 32-Bit-Ganzzahlen in einem 64-Bit-Register. Es hat per se keinen 64-Bit-Multiplikationsbefehl . Um zu multiplizieren, führen Sie eine Multiplikations-Addierung durch, die das Nullregister addiert. Es gibt eine ganzzahlige Division, aber keinen ganzzahligen Modul.
  • Die RISC-V ISA mit der M-Erweiterung hat mehrere Varianten von MULH, MUL, DIVund REM, aber das RISC-V Instruction Set Manual empfiehlt, dass Mikroarchitekturen verwendet werden, wenn a von a mit denselben Quelloperanden oder a von a mit denselben Quelloperanden MULHgefolgt wird kann diese Operationen zu einer einzigen Operation verschmelzen, anstatt zwei getrennte auszuführen.MULDIVREM

Es ist definitiv möglich, das vollständige Ergebnis einer Multiplikation oder Division und Rest gleichzeitig zu berechnen. Mehrere RISC ISAs haben dazu eine Anweisung! Theoretisch könnten Sie, wenn Sie nur ein Ergebnis wünschen, das Ziel des anderen auf das Nullregister setzen. Warum hat dann kein RISC-ISA, den ich mir angesehen habe, eine Anweisung, sowohl den Quotienten als auch den Rest in separaten Mehrzweckregistern zu speichern, und warum hat der eine ISA, den ich gefunden habe, der dies für die hohen und niedrigen Wörter eines Produkts tut, ARM A32, lassen Sie es in der nächsten großen Revision fallen?

Mich interessiert besonders, warum die frühen RISC-Chips Mitte der 80er diese Designwahl getroffen haben. SPARC V8 hatte kein Befehlsformat mit zwei Quell- und zwei Zielregistern und wollte möglicherweise seinen Decoder nicht mit einem anderen Format verkomplizieren. MIPS I: Abstriche machen, um komplexere Anweisungen in eine klassische RISC-Pipeline zu integrieren?

Aber ich frage mich auch, warum sich moderne RISC-Architekturen davon entfernt haben. Ich vermute, ARM A64 macht das so, weil Multiplizieren-Addieren mit fester Genauigkeit für die Multimedia-Decodierung nützlich ist, mit weniger relativem Overhead, je mehr Bits Sie multiplizieren, und sobald Sie das haben, ist es sinnvoll, die Schaltungen für die Multiplikation wiederzuverwenden ( füge einfach 0 hinzu). Aber die RISC-V-Dokumentation schlägt vor, dass der Kern eine einzige Operation haben sollte, die beide Ergebnisse berechnet, also warum nicht in einem RISC-Design diese in der ISA verfügbar machen?

Gibt es irgendwelche veröffentlichten Artikel, die sich mit diesem Thema befassen? Oder haben die Designer dieser Architekturen jemals ihre Gründe dafür erklärt?

Warum? Warum nicht? Nur weil etwas möglich ist, heißt das nicht, dass es aus verschiedenen Gesichtspunkten wünschenswert ist, Wirtschaft, Kultur, Bereich, SPARC hat das versucht, also werden wir etwas anderes machen, unser Compiler würde es nicht mögen, unser Compiler-Typ das suggeriert, das ist gerade weg, also lass es uns nicht tun ... Wissen Sie, wie kompliziert die Büropolitik wird, wenn Sie eine mehr oder weniger willkürliche Entscheidung für den Kunden treffen müssen?
War das denn so willkürlich? Oder gab es einen bestimmten Grund, warum es nicht möglich war? Oder keine sinnvolle Optimierung?
Eigentlich eine gute Frage. Sie können sich die adsp-2100-Serie wegen ihrer speziellen div-Anweisungen ansehen. Das konnte man gebrauchen, aber es war immer nur ein bisschen gut. Ich habe viele Routinen geschrieben, um Divisionen sowohl für FP als auch für ganzzahlige Verwendungen durchzuführen, und ich kann immer beide so einfach wie nur eine von ihnen bereitstellen. Gleiche Zeit, gleiche Zyklen. Also ja, eine gute Frage.
Nur als Randbemerkung, da Sie anscheinend recherchiert haben. Vor Jahrzehnten hat Bipolar Integrated Technologies den ersten vollständig kombinatorischen FP-Teilungs-IC entwickelt. Sie sind jetzt schon lange weg.
@jonk Huh. Der Vorläufer von RISC, der IBM 801, hatte auch eine Divisionsschrittanweisung. mirrorservice.org/sites/www.bitsavers.org/pdf/ibm/system801/… (42)
Ich habe nichts Neues über den Adsp-2100 gesagt. Nur darauf aufmerksam machen. Ich werde mir die von Ihnen erwähnten Dokumente ansehen, wenn ich einen Moment Zeit habe, und sehen, wie sie verglichen werden. Ich glaube jedoch, dass die BIT FP Div eine Premiere in ihrer Größenordnung war. Und seitdem vielleicht nicht mehr umgezogen. Wäre aber daran interessiert, falsch zu liegen.
Nur Gedanken ... Multiplizieren und Dividieren verwenden im Vergleich zu anderen CPU-Operationen eine strafend tiefe Logik, und das macht sie oft bei weitem am langsamsten zu implementieren. Ich kann also begründen, warum ein CPU-Design seine MUL und DIV vom Kern entkoppelt, um die Geschwindigkeit der restlichen Befehlsausführung zu erhöhen. Ich frage mich, ob die Reste aus einer tieferen Logikstufe stammen als das DIV-Ergebnis. Da es aus dem DIV-Ergebnis leicht genug berechnet werden kann, könnte es sinnvoll sein. Keine Ahnung, ob dir das sowieso klar war oder ob es hilft :-)
Dies ist es eindeutig wert, geschlossen zu werden, da es hauptsächlich auf Meinungen basiert. Es gibt keine wirkliche Antwort, Sie müssten jedes der Designteams für jede Architektur aufsuchen. Vielleicht verstehen Sie, wie bereits erwähnt, 1) Sie haben SEHR Glück, überhaupt eine Hardwareteilung zu haben, wenn Sie eine haben 2) ebenso für Multiplizieren, aber es ist nicht so schmerzhaft und wird häufiger implementiert als Teilen. Für viele Taktzyklusoperationen benötigen sie nur minimale Mengen an Logik, aber wenn Sie sich einem einzelnen Taktzyklus nähern, können sie beginnen, den gesamten Rest des Designs zu überschwemmen
Aus diesem Grund sehen Sie auf einigen Armkernen beispielsweise eine Option, um eine Einzelzyklus-Multiplikation oder eine Mehrfachzyklus-Multiplikation zu verwenden. Sparen Sie auf Chip-Immobilien. Ich bin mir über den Rest Ihrer Frage nicht sicher. Es ist offensichtlich, dass eine Multiplikation von Nbits = Nbits * Nbits etwas nutzlos und verschwenderisch ist. Jetzt ist ein Nbit = Nbit/Nbit nützlicher. Wenn Sie sich darüber beschweren, dass Sie nicht alle Register gut auswählen können, was einfach zu schlecht ist, gibt es wahrscheinlich offensichtliche Gründe dafür, die jedoch wahrscheinlich architekturspezifisch sind. und wenn Sie sich in einer Einzeltakt-Betriebslösung befinden, hat die separate Berechnung des Modulo möglicherweise einen gültigen Kompromiss.
@old_timer Falls sich irgendjemand fragt, warum diese Frage plötzlich wieder Aufmerksamkeit erregt, sieben Monate nachdem sie ursprünglich geschlossen wurde, liegt das daran, dass ein anderer Benutzer sie bearbeitet hat, um „eine klare Ruf-zu-den-Waffen-Frage“ hinzuzufügen. Und dann fing es an, mehr Kommentare und Stimmen zu bekommen. Also dachte ich, ich könnte das genauso gut erweitern.

Antworten (1)

Im Allgemeinen sind die geschäftlichen Anforderungen an Prozessordesigns darauf ausgerichtet, entweder synthetische Benchmarks oder reale Anwendungs-Workloads zu erfüllen . Funktionen, die keines davon ansprechen, sind schwerer zu verkaufen und werden daher eher ausgelassen.

In den 80er Jahren war der Dhrystone-Benchmark sehr beliebt. Normalerweise würde dies in einer Hochsprache dargestellt, normalerweise enthält C. Dhrystone keine Restoperationen! Designer, die auf einen hohen Dhrystone-Score abzielen, könnten also die Restoperation weglassen, um die Zykluszeit um einige Pikosekunden zu verkürzen.

Die meisten Hochsprachen haben separate arithmetische Operatoren für Quotient und Rest, nur sehr wenige bieten standardisierte Operationen, um beides aus einer einzigen Operation zu erhalten (teilweise, weil C und FORTRAN keine nativen Tupel haben!). Bis vor kurzem waren Compiler nicht besonders gut darin, Optimierungen zu erkennen, die es ihnen ermöglichen würden, zwei Operationen in eine Anweisung zu falten.

Wenn wir uns die Art von arithmetischer Arbeit ansehen, für die Prozessoren optimiert wurden , kümmern sie sich tendenziell nicht um Reste. Die großen Beispiele sind FFT und Matrixmultiplikation für lineare Algebra. Aus diesem Grund neigen Prozessoren dazu, Anweisungen zum Multiplizieren und Akkumulieren und SIMD-Anweisungen zu haben.

Danke schön! Ich war teilweise motiviert, ein Problem mit einem schnellen Algorithmus zu lösen, der viele %Operationen erforderte. Die C-Standardbibliothek hat div()und ldiv(), die zweigliedrige Strukturen zurückgeben und Hardware nutzen sollen, die sowohl den Quotienten als auch den Rest auf einmal findet. Compiler können diese Optimierung jetzt normalerweise automatisch durchführen, diese Anweisungen sind (wie bereits erwähnt) weniger verbreitet und /wurden %so gut spezifiziert wie div()und ldiv()seit C99. Es ist nicht mehr wirklich ein Vorteil, sie zu verwenden, und ich frage mich, wie viel Code es jemals getan hat.
Da Sie FFT ansprechen, stellt sich die Frage: War der Primfaktor-FFT-Algorithmus von Good-Thomas relativ unbeliebt, weil er modlangsam war, und modlangsam, weil Algorithmen wie Good-Thomas nicht als wichtig angesehen wurden?
div/mod war schon immer langsamer als die Multiplikation, was meiner Meinung nach diese Wahl beeinflusst haben muss. Viele Architekturen haben Single-Cycle-Multiplikation und langsamere Division; Einige der Mikrocontroller haben eine Multiplikation, aber überhaupt keine Division.
Auf der Multiplikationsseite fällt auf, dass es bis C99 den Typ "long long" hinzufügte, keine Möglichkeit gab, eine 32x32->64-Multiplikation auf einem 32-Bit-ILP32-System in Standard-C zu schreiben, und es gibt immer noch keine Möglichkeit, eine 64x64- >128 multiplizieren in Standard C.