Binäre Division und Modulo in Hardware

  • Allgemein

Es gibt 5 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    Binäre Division und Modulo in Hardware

    Bezogen auf diesen Thread habe ich mich jetzt doch entschieden, Multiplikation, Division und Modulo in Hardware zu implementieren.
    Wie Multiplikation funktioniert ist mir klar und ich weiß auch schon genau, wie ich das machen werde.
    Mit der Division bin ich aber leicht überfordert. Ich habe einige Erklärungen zu iterativen Algorithmen gefunden, aber ich brauche ein Verfahren, das immer "gleich lange dauert", also ich lege die Spannungen an den Eingängen an und habe sofort (natürlich mit einer kleinen Verzögerung) das Ergebnis an den Ausgängen.


    Und ich habe ein weiteres Problem:
    Bei der Multiplikation C = A * B sind A und B 8-Bit Werte und das Ergebnis C ein 16-Bit Wert. Das macht Sinn.
    Aber wie sieht es bei der Division C = A / B aus? Da C nie größer A sein kann (weil B nicht kleiner 1 sein kann), würde es Sinn machen, wenn A ein 16-Bit Wert ist und B und C ein 8-Bit Wert, sodass man die Multiplikation vom Prinzip her genau umdrehen kann. Aber da man auch durch 1 dividieren kann, kann es Sinn machen, dass C auch ein 16-Bit Wert ist.

    Wie müsste eine Schaltung aussehen? Am besten wäre es, wenn diese Schaltung mit einer Steuerleitung zwischen Division und Modulo umschalten kann. Wenn das nicht direkt geht, wäre es auch möglich, das Ergebnis der Division von A zu subtrahieren, aber das möchte ich vermeiden, wenn es anders geht.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Ich hab keine Ahnung von Elektronik, deshalb kann ich dir nicht wirklich helfen, aber hierzu will ich mich äußern:

    Niko Ortner schrieb:

    aber ich brauche ein Verfahren, das immer "gleich lange dauert"
    Jeder mir bekannte Prozessor braucht länger für eine Division als für Multiplikation, Addition und Division (als Ganzzahloperationen). Ich denke nicht, dass das so designt wäre, wenn es sich lohnen würde es anders zu machen. Also warum versuchst du es so?
    Eine Ganzzahldivision ist nunmal eine Folge aus Subtraktionen.
    @Artentus
    Jeder Befehl hat eine feste Länge in Ticks. Zum Beispiel dauert das Kopieren von einem Register in ein anderes 4 Ticks (2 zum Lesen des OpCodes, 2 zum Kopieren des Wertes und zum Inkrementieren des ProgramCounters):

    Dass ein Befehl unterschiedlich lange dauern kann ist nicht vorgesehen.

    Ich hab keine Ahnung von Elektronik

    Ein Funktionsprinzip würde mir schon reichen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Funktionsprinzip
    SChau dir mal schriftliches dividieren zur Basis 2 an:
    exploringbinary.com/binary-division/
    Die maximal nötige Anzahl an Iterationen im Algorithmus sollte der Anzahl an Bits entsprechen (also maximal 8 Iterationen für 8 Bit usw.), da du Stellen nach dem Komma ja nicht berücksichtigst.
    Jede Iteration erfordert einen Vergleich und eine Subtraktion.
    Also die binäre Division hab ich jetzt verstanden. Ich habe mich dazu entschieden, dass bei C = A / B A und C 16-Bit Zahlen sind und B eine 8-Bit Zahl ist. Dadurch kann es keinen Überlauf geben.
    Das bedeutet, dass bei der Division 16 Schritte benötigt werden und ich bin draufgekommen, dass man diese 16 Schritte immer gleich machen kann.
    Bei jedem Schritt wird das Zwischenergebnis vom vorigen Schritt genommen, rechts das nächste Bit aus A dran gehängt und das dann wird B davon subtrahiert. Ist das Ergebnis negativ (der Subtrahierer schaltet den Borrow-Ausgang auf logisch 1), dann ist das entsprechende Bit von C 0 und das nächste Zwischenergebnis ist das vorherige Zwischenergebnis mit dem drangehängten Bit. Ansonsten ist das Bit von C 1 und das nächste Zwischenergebnis ist das Ergebnis der Subtraktion.
    Also Pseudocode-mäßig so:

    VB.NET-Quellcode

    1. Dim Dividend As Integer = &H89E2
    2. Dim Divisor As Integer = &H9C
    3. Dim Quotient As Integer = 0
    4. Dim TempResult As Integer = 0
    5. For i = 15 To 0 Step -1 'Alle Bits von links (MSB) nach rechts (LSB) durchlaufen
    6. TempResult = (TempResult << 1) Or ((Dividend >> i) And 1) 'Nächstes Bit aus Divident an Zwischenergebnis hängen
    7. Dim Difference = TempResult - Divisor 'Subtrahieren
    8. Dim Borrow = ((Difference >> 31) And 1) <> 0 'Ergebnis der Subtraktion war negativ
    9. Quotient = Quotient Or (If(Borrow, 0, 1) << i) 'Bit von Quotient setzen
    10. If Borrow Then
    11. 'Zwischenergebnis bleibt so
    12. Else
    13. 'Neuew Zwischenergebnis verwenden
    14. TempResult = Difference
    15. End If
    16. Next
    17. Dim Remainder = TempResult


    Einen solchen Schritt in Hardware zu basteln ist nicht allzuschwierig, aber ich will versuchen, Bauteile zu sparen. Das heißt, für jeden Schritt einen ganzen 16-Bit Subtrahierer zu verwenden wäre übertrieben.
    Ich bin mal alle möglichen Divisionen durchgegangen und habe die Zwischenergebnisse gespeichert. Das längste Zwischenergebnis ist 9 Bits lang. Kann das jemand bestätigen?
    Der Code oben zeigt genau dieses Beispiel, also 35298 / 156 = 226 + 42 Rest.
    Diese Division sieht so aus:
    Spoiler anzeigen

    Brainfuck-Quellcode

    1. 1000´1001´1110´0010 : 1001´1100 = 0000´0000´1110´0010
    2. 1
    3. - 0
    4. ----
    5. 10
    6. - 0
    7. -----
    8. 100
    9. - 0
    10. ------
    11. 1000
    12. - 0
    13. -------
    14. 1000´1
    15. - ´0
    16. ---------
    17. 1000´10
    18. - ´ 0
    19. ----------
    20. 1000´100
    21. - ´ 0
    22. -----------
    23. 1000´1001
    24. - ´ 0
    25. ------------
    26. 1000´1001´1
    27. - 100´1110´0
    28. --------------
    29. 11´1011´11
    30. - 10´0111´00
    31. ---------------
    32. 1´0100´111
    33. - 1´0011´100
    34. ----------------
    35. ´ 1´0110
    36. - ´ ´ 0
    37. -----------------
    38. ´ 1´0110´0
    39. - ´ ´ ´0
    40. -------------------
    41. ´ 1´0110´00
    42. - ´ ´ ´ 0
    43. --------------------
    44. ´ 1´0110´001
    45. - ´ 1´0011´100
    46. ---------------------
    47. ´ ´ 10´1010
    48. - ´ ´ ´ 0
    49. ----------------------
    50. ´ ´ 10´1010


    Ich bin jetzt gerade am Überlegen. Es müsste möglich sein, das Zwischenergebnis (welches nach dem letzten Schritt automatisch der Rest ist) zwischenzuspeichern und immer "häppchenweise" 4 Schritte zu machen. Dadurch würde man nur 4 Subtrahierer benötigen, dafür aber mehr Zeit, was aber OK ist.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    OK, ich denke ich hab die Lösung.
    Ich bin auch drauf gekommen, dass Multiplikation nach einem ähnlichen Prinzip in Zwischenschritte aufgeteilt werden kann, deshalb hab ich das gleich mit eingebaut. Das sieht vom Konzept her so aus:

    Die grünen Leitungen sind Datenleitungen, die orangen sind Steuerleitungen. Also wenn beispielsweise bei Temp die W-Steuerleitung(W steht für Write) und bei Rest/ProduktH die R-Steuerleitung (R steht für Read) aktiv ist, dann wird der Wert von Rest/ProduktH nach Temp kopiert. Ist die R-Steuerleitung stattdessen nicht aktiv, wird 0 ins Temp Register geschrieben.
    Die Addierer und Subtrahierer sehen so aus:

    Also es werden immer 2 Bits pro Durchgang berechnet.
    Da die Multiplikation zwei 8-Bit Zahlen zu einer 16-Bit Zahl verknüpft und das höherwertige Byte das letzte Zwischenergebnis ist, wird beim Multiplizieren nur eine Hälfte des Quotient/ProduktL-Registers gefüllt. Die andere hälfte bleibt unverändert. Das ist zwar nicht ganz so schön, aber einfacher zu implementieren. Und da man das Ergebnis sowieso nur Byte-weise angreifen kann, muss man sowieso zwei mal lesen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils