Hilfe für Problem mit Floating Points in Emulator

  • Hilfe für Problem mit Floating Points in Emulator

    Hallo liebe C64 Community,

    nach wochenlanger Suche habe ich mich dann doch mal entschlossen, Kontakt aufzunehmen, da ich mit meinem Problem einfach nicht weiterkomme :)
    Ja, ich erfülle mir gerade einen langgehegten Traum, und baue den Computer meiner Kindheit, zumindest in Software nach. Auf gut Deutsch, ich schreibe meinen eigenen C64-Emulator (in C#). Primäres Ziel: Spass am Programmieren :)
    Dank der ausgezeichneten C64 Wikis (de/en) und der wirklich guten Beschreibungen dort, bin ich auch relativ fix vorrangekommen. Aktueller Stand: Der 6510 verrichtet auch schon ganz gut seine Arbeit (VIC CIA etc fehlen noch - sind quasi Future Work) - der KERNAL wird gebootet und BASIC meldet sich auch freundlich.

    Mein aktuelles Problem: Die Fließkommarechnungen bzw Ausgabe von Fließkommazahlen ist nicht ganz das, was es sein sollte.

    PRINT 1/2
    .50390625

    allerdings
    PRINT 0.5
    .50234375

    Wochenlanges debuggen, googeln, etc hat mich leider nicht weiter gebracht. Auch das Analysieren der entsprechenden KERNAL-Routinen gab mir nicht die zündende Idee.

    Ich vermute stark, dass ein Status Flag irgendwo noch falsch gesetzt wird...?

    Ich hoffe, dass es hier Experten gibt, die sich mit den Routinen auskennen und/oder selbst mal einen Emulator geschrieben haben und/oder auf ein ähnliches Problem gestoßen sind.

    Anbei einen Screenshot des aktuellen Emulators.

    Quellcode des Emulators kann ich gerne zur Verfügung stellen.

    .50390625 --> 0.10000001 (binary)
    was mir nahe legt, dass irgendwie eine 1 "zu wenig" weggeshiftet, wird, oder ähnliches?

    Die Frage daher: Hat Jemand eine Idee/einen Tipp, wo ich noch ansetzen könnte bzw was genau falsch laufen könnte?


    Viele Grüße und vielen Dank schon mal, für etwaige Ideen :)
    niko
    Bilder
    • c64.png

      6,78 kB, 639×429, 15 mal angesehen

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von niko ()

  • Helfe gerne, wenn ich kann. Hab selber oft genug einen 6502-Emulator geschrieben, und da kam es auch vor, daß die Floatingpointroutinen versagt haben. In meinem Fall lag das damals daran, daß ich einen Bug in den Verschiebe-Befehlen ROR, ROL, ASL und LSR hatte, da diese intensiv gebraucht werden auch z. B. in der für Verschiebebefehle sonst eher seltenen Adressierungsart zp,x. Bei dieser Befehlsgruppe würde ich an Deiner Stelle daher als erstes nachgucken. Und natürlich werfe ich auch gerne einen Blick auf den Quellcode, wenn es DIr nichts ausmacht.
  • niko schrieb:

    Mein aktuelles Problem: Die Fließkommarechnungen bzw Ausgabe von Fließkommazahlen ist nicht ganz das, was es sein sollte.
    Teste deinen Emulator doch mal mit einer der 6502-Testsuites. Die von Wolfgang Lorenz ist praktischerweise auch gleich für den C64 geschrieben und wenn die keinen Fehler in den Opcodes mehr meldet, funktioniert wahrscheinlich auch die Fliesskommaberechnung korrekt.

    Quellcode

    1. 10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    2. 20 forj=1to5:printchr$(rnd(1)*16+70);:next
    3. 30 printint(rnd(1)*328)-217

    sd2iec Homepage
  • Also die Implementierungen meiner Shift-Befehle sind folgende:
    (Wobei die jeweiligen "konkreten" Befehle diese dann aufrufen - Bsp: ROL_26 ruft Rol() entsprechend auf.).

    Quellcode

    1. /// <summary>
    2. /// Logical ROL and set flags
    3. /// </summary>
    4. /// <param name="valueA"></param>
    5. /// <param name="valueB"></param>
    6. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    7. private ushort Rol(ushort value)
    8. {
    9. bool newCarry = (value & 0x80) != 0;
    10. value = (ushort)((value << 1) & 0x00FF);
    11. if (_CarryFlag)
    12. {
    13. value += 1;
    14. }
    15. _CarryFlag = newCarry;
    16. _ZeroFlag = (value == 0);
    17. _NegativeFlag = ((value & 0x80) != 0);
    18. return value;
    19. }
    20. /// <summary>
    21. /// Logical ROR and set flags
    22. /// </summary>
    23. /// <param name="valueA"></param>
    24. /// <param name="valueB"></param>
    25. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    26. private ushort Ror(ushort value)
    27. {
    28. bool oldCarry = _CarryFlag;
    29. _CarryFlag = ((value & 0x01) != 0);
    30. value = (ushort)(value >> 1);
    31. if (oldCarry)
    32. {
    33. value += 0x80;
    34. }
    35. _ZeroFlag = (value == 0);
    36. _NegativeFlag = ((value & 0x80) != 0);
    37. return value;
    38. }
    39. /// <summary>
    40. /// Logical ASL and set flags
    41. /// </summary>
    42. /// <param name="valueA"></param>
    43. /// <param name="valueB"></param>
    44. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    45. private ushort Asl(ushort value)
    46. {
    47. _CarryFlag = ((value & 0x80) != 0);
    48. value = (ushort)((value << 1) & 0x00FF);
    49. _NegativeFlag = ((value & 0x80) != 0);
    50. _ZeroFlag = (value == 0);
    51. return value;
    52. }
    53. /// <summary>
    54. /// Logical LSR and set flags
    55. /// </summary>
    56. /// <param name="value"></param>
    57. /// <returns></returns>
    58. private ushort Lsr(ushort value)
    59. {
    60. _CarryFlag = ((value & 0x01) != 0);
    61. value = (ushort)(value >> 1);
    62. _NegativeFlag = ((value & 0x80) != 0);
    63. _ZeroFlag = (value == 0);
    64. return value;
    65. }
    Alles anzeigen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von niko ()

  • Testsuites habe ich ausprobiert - soweit ich es hinbekommen habe.

    Die von Klaus Dormann habe ich mittels AS65 assembler in eine binary umgewandelt.
    Soweit ich dies jetzt verstehe, arbeitet der Emulator den Code so lange ab, bis er in einer Endlosschleife hängen bleibt (=> Fehlerfall oder am Ende, alles OK)

    Zitat:
    ; Most of the code is written sequentially. if you hit a trap, check the
    ; immediately preceeding code for the instruction to be tested. Results are
    ; tested first, flags are checked second by pushing them onto the stack and
    ; pulling them to the accumulator after the result was checked. The "real"
    ; flags are no longer valid for the tested instruction at this time!

    Mein Problem hier: Ich bekomme das "Mapping" ASM Code vs Speicheradresse der Trap nicht hin, bzw finde diese nicht.
    Mein Emulator bleibt auch tatsächlich in einem Loop hängen, aber welcher der "Traps" dies ist, ergo welcher Befehl fehlerhaft ist, ist mir da leider nicht klar.

    Die Testsuite von Wolfgang Lorenz bekomme ich leider nicht ans Rennen - mir ist da unklar, wie ich die in ein ROM-File assembliere
    Die Beschreibungen da bringen mich leider nicht weiter :)
  • niko schrieb:

    Die Testsuite von Wolfgang Lorenz bekomme ich leider nicht ans Rennen - mir ist da unklar, wie ich die in ein ROM-File assembliere
    Mindestens eine Version davon sollte als Sammlung von PRG-Dateien (evtl. aufgeteilt auf mehrere D64-Files) daherkommen - die laufen einfach aus dem RAM und verwenden die normalen C64-ROM-Kernal-Aufrufe für I/O. Deinem Screenshot nach starten Kernal und BASIC ja schon. Das Nachladen kann man auch einfach nachbauen, indem man die Emulation bei PC=$ffd5 kurz unterbricht und den Ladevorgang extern simuliert (Adresse des Filenamens steht in $bb/$bc, Länge in $b7, Ladeadresse aus den ersten beiden Byte der Datei holen, Daten dahin laden, PC auf $f5ae setzen)

    Quellcode

    1. 10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    2. 20 forj=1to5:printchr$(rnd(1)*16+70);:next
    3. 30 printint(rnd(1)*328)-217

    sd2iec Homepage
  • niko schrieb:

    PRINT 1/2
    .50390625

    allerdings
    PRINT 0.5
    .50234375

    .50390625 --> 0.10000001 (binary)
    .50390625 --> .81000000 (hex)
    .50234375 --> .80999999 (hex)
    Lauter Neunen in Hexdarstellung ist ein sehr komischer Zufall - ist möglicherweise der Dezimalmodus aktiv, obwohl er es nicht sein sollte?
    Yes, I'm the guy responsible for the ACME cross assembler
  • Hiho,

    @Mac Bacon: Nein, ist er nicht :) habe den Dezimalmodus auch noch nicht implementiert

    @Unseen: Ich habe es nun hinbekommen die einzelnen Files der Testsuite von Wolfgang Lorenz zu laden. Vielen Dank für die Hilfe!!!!

    Mittels
    LOAD "START"
    RUN
    lädt er nun auch entsprechend die Einzeltests von Wolfgang Lorenz nach (siehe Screenshot).

    Dazu habe ich allerdings noch ein paar Fragen :)

    1) mir ist die Ausgabe nicht ganz klar:
    Display:
    before data accu xreg yreg flags sp
    after data accu xreg yreg flags sp
    right data accu xreg yreg flags sp
    ---> Woran erkenne ich hier genau, ob es nun richtig ist oder falsch :)

    2) Aktuell muss ich nach jeder "Testiteration" eine Taste drücken, damit es weiterläuft. Kann man das irgendwie quasi "komplett durchlaufen" lassen?
    Bilder
    • test_suites.png

      58,2 kB, 869×537, 15 mal angesehen
  • niko schrieb:

    ---> Woran erkenne ich hier genau, ob es nun richtig ist oder falsch :)
    Wenn es erscheint ist was falsch. Bei einem fehlerfreien Durchlauf erscheinen lediglich die Namen der geladenen Tests.

    2) Aktuell muss ich nach jeder "Testiteration" eine Taste drücken, damit es weiterläuft. Kann man das irgendwie quasi "komplett durchlaufen" lassen?
    Ja, Fehler beseitigen.

    Quellcode

    1. 10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    2. 20 forj=1to5:printchr$(rnd(1)*16+70);:next
    3. 30 printint(rnd(1)*328)-217

    sd2iec Homepage
  • Scheint aber nur das Break-Flag unterschiedlich zu sein, oder? Das sollte m.E. keinen Einfluss auf Float-Berechnungen haben können...
    ────────────────────────────────────────────────────────────
    Time of Silence - Time of Silence 2 Development Blog
    ────────────────────────────────────────────────────────────
  • Claus schrieb:

    Scheint aber nur das Break-Flag unterschiedlich zu sein, oder? Das sollte m.E. keinen Einfluss auf Float-Berechnungen haben können...
    Schon, aber die Testsuite wird es trotzdem bei jedem einzelnen Test anmeckern und das ist ja schnell gefixt.

    Ausserdem: Sollte man wirklich nur den Float-beeinflussenden Emulationsfehler beseitigen oder nicht doch lieber alle?

    Quellcode

    1. 10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    2. 20 forj=1to5:printchr$(rnd(1)*16+70);:next
    3. 30 printint(rnd(1)*328)-217

    sd2iec Homepage
  • Ja, ich habe das Break flag falsch gesetzt bzw hatte es auch falsch verstanden.
    Mein Verständnis jetzt: Break ist immer gesetzt, wird aber bei BRK gesetzt auf den Stack gelegt und bei einem Hardwareinterrupt gelöscht auf den Stack gelegt... ok.
    Das ist jetzt gefixt.

    Jetzt bleibe ich wegen dem Carry-Flag hängen (siehe Screenshot) :)
    sobald ich bei 0x80 "ankomme".

    Und ich stimme Unseen zu, natürlich möchte ich gerne, soweit möglich, alle Fehler fixen... na, dann weitersuchen :)
    Bilder
    • carry.png

      66,43 kB, 869×536, 12 mal angesehen
  • niko schrieb:

    Ja, ich habe das Break flag falsch gesetzt bzw hatte es auch falsch verstanden.
    Mein Verständnis jetzt: Break ist immer gesetzt, wird aber bei BRK gesetzt auf den Stack gelegt und bei einem Hardwareinterrupt gelöscht auf den Stack gelegt... ok.
    Das Break-Flag gibt es in der CPU überhaupt nicht =) Das wird nur spontan erzeugt, wenn die Flags auf den Stack abgelegt werden: Normalerweise wird eine 1 reingeschrieben, einzig bei einer BRK-Anweisung wird dort eine 0 erzwungen.

    Jetzt bleibe ich wegen dem Carry-Flag hängen (siehe Screenshot) :-)
    sobald ich bei 0x80 "ankomme".
    Das Carry-Bit wird nur von Addition/Subtraktion, Shiftbefehlen und beim expliziten überschreiben (CLC/SEC/PLP) beeinflusst, warum beeinflusst dein LDA das überhaupt?

    Quellcode

    1. 10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    2. 20 forj=1to5:printchr$(rnd(1)*16+70);:next
    3. 30 printint(rnd(1)*328)-217

    sd2iec Homepage
  • Mein LDA beeinflusst das Carry-Bit gar nicht - das ist das Merkwürdige an der Sache :-/
    Es muss ein anderer Befehl sein, der, sobald der Counter 128 erhält das Carry-Bit umsetzt. Die einzigen Befehle, die ich mir vorstellen könnte, sind ROL und ASL. Die sind bei mir wie folgt implementiert:

    Quellcode

    1. private ushort Rol(ushort value)
    2. {
    3. bool newCarry = (value & 0x80) != 0;
    4. value = (ushort)((value << 1) & 0x00FF);
    5. if (_CarryFlag)
    6. {
    7. value += 1;
    8. }
    9. _CarryFlag = newCarry;
    10. _ZeroFlag = ((value & 0x00FF) == 0);
    11. _NegativeFlag = ((value & 0x80) != 0);
    12. return value;
    13. }
    14. private ushort Asl(ushort value)
    15. {
    16. value = (ushort)(value << 1);
    17. _CarryFlag = ((value & 0xFF00) != 0);
    18. _NegativeFlag = ((value & 0x80) != 0);
    19. _ZeroFlag = ((value & 0x00FF) == 0);
    20. return (ushort)(value & 0x00FF);
    21. }
    Alles anzeigen
  • niko schrieb:

    Mein LDA beeinflusst das Carry-Bit gar nicht - das ist das Merkwürdige an der Sache :-/
    Es muss ein anderer Befehl sein, der, sobald der Counter 128 erhält das Carry-Bit umsetzt. Die einzigen Befehle, die ich mir vorstellen könnte, sind ROL und ASL.
    So sieht der Innenteil der Testschleife aus:

    Quellcode

    1. cmd lda #0 ; Argument wird modifiziert
    2. php
    3. cld
    4. sta aa
    5. stx xa
    6. sty ya
    7. pla
    8. sta pa
    9. tsx
    10. stx sa
    11. jsr check ; vergleicht Speicher mit Speicher
    Alles anzeigen
    Ich würde also zuerst LDA und PHP verdächtigen.
    Yes, I'm the guy responsible for the ACME cross assembler
  • niko schrieb:

    bool newCarry = (value & 0x80) != 0;
    Hat das denn so seine Richtigkeit? Oder fehlen da Klammern? Ginge das nicht mit "_newCarry = ((value & 0x80) !=0);" ?

    (Hab aber nahezu null Ahnung von C...)
    Read'n'Blast my jump'n'stop-text-sprite-scroller Select A Move

    Ex-TLI (The Level 99 Industries) & Ex-TNP (The New Patriots) & Ex-TEA (The East Agents) & ?
  • Juhu!!!

    Ich habe meinen (wirklich dummen!!) Fehler gefunden (siehe Quellcode).
    Beim Widerherstellen von SR (durch PLP und RTI) habe ich NUR die vorher gesetzten flags hergestellt. Wenn ein flag aktuell gesetzt war und es beim Widerherstellen nicht gesetzt sein soll, habe ich es nicht auf false gesetzt ( die Else-Zweige im setter fehlten... OMG... )...

    Danke an alle für die Tipps und vor allem die Idee und Hilfe mit der Testsuite... aktuell läuft diese jetzt ohne Probleme bis ADCB (siehe Bild)... das liegt aber daran, dass ich noch keinen decimal mode implementiert habe...
    Sehr cool! Vielen Dank nochmal... jetzt kann ich von da weiter machen und die Testsuite hilft mir, direkt Bugs zu finden... btw, durch die Tests habe ich auch noch zwei weitere Befehle gefixt :)
    Übrigens: Die floating point routinen arbeiten jetzt auch so, wie sie sollen :)

    Quellcode

    1. private ushort _SR
    2. {
    3. get
    4. {
    5. ushort value = 0;
    6. if (_NegativeFlag)value += 128;
    7. if (_OverflowFlag)value += 64;
    8. value += 32; //32 bit is unused; always 1
    9. if (_BreakFlag)value += 16;
    10. if (_DecimalFlag)value += 8;
    11. if (_InterruptFlag)value += 4;
    12. if (_ZeroFlag)value += 2;
    13. if (_CarryFlag)value += 1;
    14. return value;
    15. }
    16. set
    17. {
    18. if ((value & 128) != 0) _NegativeFlag = true; else _NegativeFlag = false;
    19. if ((value & 64) != 0) _OverflowFlag = true; else _OverflowFlag = false;
    20. //32 bit is unused; always 1
    21. _BreakFlag = true; //always true
    22. if ((value & 8) != 0) _DecimalFlag = true; else _DecimalFlag = false;
    23. if ((value & 4) != 0) _InterruptFlag = true; else _InterruptFlag = false;
    24. if ((value & 2) != 0) _ZeroFlag = true; else _ZeroFlag = false;
    25. if ((value & 1) != 0) _CarryFlag = true; else _CarryFlag = false;
    26. }
    27. }
    Alles anzeigen
    Bilder
    • testsuite_run.png

      8,23 kB, 642×372, 11 mal angesehen
  • Ich habe gesehen, dass Foren-Themen als "Erledigt" markiert werden können.
    Da mein Problem, dank der Hilfe mehrerer Mitglieder hier, gelöst wurde, würde ich es auch als gelöst markieren. Ich weiss nur leider, ob ich es darf und wie es geht :)

    Die Problemlösung war übrigens, einen Bug mit dem Speichern und Widerherstellung des Statusregisters auf dem Stack - siehe oben - zu fixen.
    Außerdem war die Ausführung der Testsuite von Wolfgang Lorenz sehr hilfreich, um Fehler zu finden und zu fixen...