Forum: Mikrocontroller und Digitale Elektronik Finetuning 6510 Emulator / C / AT91SAM7S256


von Peter Pippinger (Gast)


Lesenswert?

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

von Benedikt K. (benedikt)


Lesenswert?

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.

von Peter Pippinger (Gast)


Lesenswert?

>> 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;
}

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

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.

von Peter Pippinger (Gast)


Lesenswert?

>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 
:-)

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

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?

von Marius S. (lupin) Benutzerseite


Lesenswert?

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...

von gerhard (Gast)


Lesenswert?

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

von Marius S. (lupin) Benutzerseite


Lesenswert?

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).

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

> 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.

von Peter Pippinger (Gast)


Lesenswert?

>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.

von Tom N. (tom-nachdenk)


Lesenswert?

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

von Peter Pippinger (Gast)


Lesenswert?

@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.

von Marius S. (lupin) Benutzerseite


Lesenswert?

> 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
Noch kein Account? Hier anmelden.