Hallo NG, zunächst mal kurz meiner Freude Ausdruck verleihen: :-) :-) :-) Jetzt funktioniert mein 6510 EMU schon fast. Hier eine kleine Disassemlierung eines 64er-SID-Files - geladen von SD-Karte - auf dem ARM disassembliert - gesendet an RS232: ------ 8< ------ 8< ------ SCHNIPP Initialize Card... Card Initialized! READY. _ B009 24 F8 BIT $F8 B00B 30 2E BMI $B039 B00D 50 43 BVC $B050 B00F A2 1F LDX #$1F B011 8E 18 D4 STX $D418 B014 AE 3A B5 LDX $B53A B017 A9 00 LDA #$00 B019 BC 0F B5 LDY $B50F,X B01C 99 04 D4 STA $D404,Y B01F 9D 13 B5 STA $B513,X B022 9D 16 B5 STA $B516,X B025 9D 19 B5 STA $B519,X B028 9D 1F B5 STA $B51F,X B02B 99 06 D4 STA $D406,Y B02E A9 1B LDA #$1B B030 9D 25 B5 STA $B525,X B033 CA DEX B034 10 E1 BPL $B015 B036 85 F8 STA $F8 B038 4C 42 B4 JMP $B442 ------ 8< ------ 8< ------ SCHNAPP ...so, nun zum eigentlichen Thema: der größte Teil der Emulation läuft eigentlich in einer Switch / Case Anweisung ab. Es wird die passende Aktion zum aktuellen Opcode gesucht und ausgeführt. Das darumherum ist eigentlich immer gleich. Jetzt stellen sich mir 3 Fragen: 1. wie wird die Switch / Case Anweisung umgesetzt? Dauert es länger, wenn der letzte Case zutrifft, als wenn der erste Case zutrifft? Arbeitet der Compiler bei Switch / Case mit einer "Sprungtabelle"? 2. wie stelle ich fest, wieviele Takt-Zyklen der ARM für die Emulation eines Befehls benötigt? 3. kann ich irgendwo im C Code "echte" ARM-Assembler-NOPs einfuegen, um alle Befehle gleich lang laufen zu lassen? Vielen Dank für jeden Tip! Peter
Peter Pippinger wrote: > 1. wie wird die Switch / Case Anweisung umgesetzt? Dauert es länger, > wenn der letzte Case zutrifft, als wenn der erste Case zutrifft? Ja, das ist meistens der Fall. > Arbeitet der Compiler bei Switch / Case mit einer "Sprungtabelle"? Auch das ist möglich. Wie das bei deinem Compiler genau ist, weiß ich jedoch nicht. Allerdings wird die Tabelle meist wohl eher nur in seltenen Fällen verwendet. > 2. wie stelle ich fest, wieviele Takt-Zyklen der ARM für die Emulation > eines Befehls benötigt? Garnicht. Vor allem auf einem ARM mit seinem Instruction Cache (oder wie auch immer die das nennen) ist das nicht ohne weiteres möglich. > 3. kann ich irgendwo im C Code "echte" ARM-Assembler-NOPs einfuegen, um > alle Befehle gleich lang laufen zu lassen? Das ist Pfusch, denn mit jeder neuen Compilerversion musst du dann den Code wieder anpassen. Besser wäre vielleicht einen Timer zu verwenden, und am Ende einfach zu warten bis ein bestimmter Timerwert erreicht ist.
>> Arbeitet der Compiler bei Switch / Case mit einer "Sprungtabelle"? >Auch das ist möglich. Wie das bei deinem Compiler genau ist, weiß ich >jedoch nicht. Allerdings wird die Tabelle meist wohl eher nur in >seltenen Fällen verwendet. ...naja, möglich reicht mir nicht. Ich brauch sowas definitiv, weil ich nicht 256 Compare-Branch-Duos warten kann, bis mal der letzte Opcode Emuliert wird! Wäre ein Konstrunkt auf diese Art und Weise OK, oder ist das totaler Bullshit? BEISPIEL: --------- int o=0; int i=0; void opcode00() { i=1; } void opcode01() { i=2; } // Sprungtabelle void (*ptrs[])() = { opcode00, opcode01}; int main() { ptrs[0](); // calls opcode00 ptrs[1](); // calls opcode01 i=o; return 0; }
Ich würde das erst mal straightforward implementieren und die Optimierung dem Compiler überlassen. WENN sich dann herausstellt dass er es nicht schnell genug umsetzt kannst du immer noch von Hand optimieren. Wenn du eine sportliche Herausforderung suchst solltest du in Erwägung ziehen einen JIT-Compiler zu schreiben der den Code zur Laufzeit in ARM-Code umsetzt.
>Ich würde das erst mal straightforward implementieren und die >Optimierung dem Compiler überlassen. WENN sich dann herausstellt dass er >es nicht schnell genug umsetzt kannst du immer noch von Hand optimieren. naja, das kann einfach nicht angehen, dass z.B. der Opcode 0xfe (INC $xxxx,X) 254 * (CMP + BEQ) Vorlauf benötigen bis mal was passiert. a) Das so zu machen macht doch den Emulator zeitlich absolut unberechenbar und ineffizient. b) Soviele Takte habe ich nicht zu "verschenken". Habs jetzt so gemacht, wie oben beschrieben. Funktioniert auch. >Wenn du eine sportliche Herausforderung suchst solltest du in Erwägung >ziehen einen JIT-Compiler zu schreiben der den Code zur Laufzeit in >ARM-Code umsetzt. Nee, lieber nicht. Hab das mit Assembler doch neulich an den Haken gehängt, weil ich mit der SD-Karte nicht so recht weiterkam. Und wenn die Musik richtig spielt ist mit am Ende sowieso Wurst, wie das passiert :-)
Peter Pippinger wrote: >>Ich würde das erst mal straightforward implementieren und die >>Optimierung dem Compiler überlassen. WENN sich dann herausstellt dass er >>es nicht schnell genug umsetzt kannst du immer noch von Hand optimieren. > > naja, das kann einfach nicht angehen, dass z.B. der Opcode 0xfe (INC > $xxxx,X) 254 * (CMP + BEQ) Vorlauf benötigen bis mal was passiert. Benötigt er die wirklich, oder vermutest du das nur?
Da kann man doch einfach den debugger starten, wenn du yagarto benutzt ist auf der rechten seite ein ASM output da siehst du was er macht. Vielleicht kommt es auch auf den Optimierungsgrad an...
hallo peter, die opcode umsetzung würde ich einfach über eine tabelle mit 256 einträgen machen. und das zeitverhalten würde ich wie schon erwähnt mit einem hardware timer nachstellen. auch dafür ist die tabelle besten geeignet. stichwort: array of structures. gruss gerhard
Ich würde solch einen emulator eher in assembler schreiben. (zumindest die Kernfunktionen) Vorteil ist das du dann auf jeden fall weisst was passiert. Du könntest dann anstatt ein Array mit funktionspointern zu benutzen einen Bereich für die Opcode-Funktionen definieren und die funktionen in einem festen Offset anlegen. Wenn ich das richtig sehe hat der 6510 8 bit opcodes. Dann reservierst du für jeden Opcode einfach 64 byte (je nachdem wieviel Code du pro OPCODE brauchst) und springst dann einfach an die Addresse: Basisaddresse + OPCODE * 64 Bei Basisaddresse starten dann deine OPCODE Funktionen (die dann wirklich nur 64 byte lang sein dürfen). So sparst dir den speicher für die Sprungtabelle... Dann brauchst du: 2 Cycle zum das Laden der Basisaddresse (dort wo deine OPCODE Funktionen starten) 1 Cycle für die Addition des OPCODES * 64 (der Shift ist geschenkt) 1 Cycle zum Funktionsaufruf Von der Sprungtabelle laden wäre wohl genau so schnell: 2 Cycle zum Laden der Basisaddresse der Sprungtabelle 1 Cycle zum addieren des OPCODES * 4 (shift ist wieder geschenkt) 1 Cycle zum Funktionsaufruf Kann sein, dass ich das mit den cycles nicht ganz richtig habe :-) Die Basisaddresse könntest dir auch in nem Register zwischenspeichern. Hardcore optimierungen (so wie du es gerne hättest) gehen nur in ASM. Solche Tricks wird kein C Compiler machen. Beim ARM ist es auch so, dass man per ASM wirklich noch Speed raus holen kann (was bei größeren Systemen nix mehr bringt).
> Solche Tricks wird kein C Compiler machen.
Nachdem jetzt viel über Alternativen zum switch-case philosophiert wurde
sollte sich einfach mal jemand hinsetzen und anschauen was der Compiler
aus switch-case macht. Vielleicht geht dann manch einem ein Licht auf
woher der berühmte Satz "premature optimization is the root of all evil"
kommt.
>Nachdem jetzt viel über Alternativen zum switch-case philosophiert wurde >sollte sich einfach mal jemand hinsetzen und anschauen was der Compiler >aus switch-case macht. Vielleicht geht dann manch einem ein Licht auf >woher der berühmte Satz "premature optimization is the root of all evil" >kommt. ich habe mich mal hingesetzt, und ein neues Projekt mit nur einem Switch im Debugger angesehen. Es waren nur 3 Cases und wenig Code in diesen. Das hat der Compiler tatsächlich so gemacht, wie ich es nicht wollte. Allerdings scheint er bei aufwendigeren Cases tatsächlich eine Sprungtabelle zu verwenden. Aber das ist mir jetzt auch egal, weil ich die Schiene mit den Pointern auf Funktionen fahre... Trotzdem Danke an alle.
Auch wenn es zu spät ist, warum suchst Du bei den Opcodes nicht binär? 8 Vergleichen für jeden Befehl ... und wenn es geeignete Bitgruppen gibt evtl. noch weniger
@andreas >Wenn du eine sportliche Herausforderung suchst solltest du in Erwägung >ziehen einen JIT-Compiler zu schreiben der den Code zur Laufzeit in >ARM-Code umsetzt. ...habe mir heute auf dem Weg zur Arbeit das mal durch den Kopf gehen lassen. Aber ich denke, dass sowas eher etwas für interpretierte Sprachen geeignet ist. Stell Dir mal vor, dass ich einen Opcode an einer Speicherstelle per Programm ändere. Und an dieser Stelle steht nun schon der umgewandelte ARM-Code... Ich denke, dass das für Assembler nahezu unmöglich ist. Meine neue Herausforderung wird wohl in nächster Zeit die Renovierung eines alten Häuschens sein. Da wird wohl leider noch weniger bis gar keine Zeit für den uC übrig bleiben. Vielleicht findet sich ja danach jemand, der das mit mir zusammen machen möchte. Ich denke, dass das Projekt mehr als interessant ist. Nur schaffe ich es zeitlich einfach nicht :-( PS.: ich bin z.Zt. eigentlich schon wieder eher dafür, das Ganze doch in Assembler zu machen.
> Vielleicht findet sich ja danach jemand, der das mit mir zusammen machen > möchte. Ich bezweifle mal, dass dir hier jemand beim renovieren helfen wird ;-)
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.