Emulator in Python...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

darktrym hat geschrieben:Wollte mal fragen, nutzt dein Emulator eigentlich auch fortschrittliche Technologien wie Recompiling?
Ne, es ist der "klassische Weg" ohne Compiler... Wozu auch? Die Performance wird reichen. Ob mit CPython, weiß ich nicht nicht, auf jeden Fall aber mit PyPy...
Auf dem Handy soll es eh nicht. Dort ist Python eh nicht so richtig angekommen...

Optimierungen sind aber noch außen vor. Auch, kann man einige Dinge schöner machen...

Momentan geht es mir erst mal ums Funktionieren. Leider sind irgendwo in den tiefen noch Bugs. Sind aber schwer zu finden. Weil man halt tief in Assembler einsteigen muß.
Deswegen die Idee mit dem "Code Coverage" s. http://www.python-forum.de/viewtopic.php?f=1&t=34236 um sich auf die Code Teile zu konzentrieren, die noch nicht getestet sind.

Funktioniert alles und ist auch schön mit Unittests festgenagelt, dann geht es ums schöner machen und Optimierungen.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Recompiling bezieht sich nicht auf Handy sondern eher die Verlagerung der Interpretation. Statt einen Interpreter schreiben der Assembler interpretiert, den Code umschreiben lassen bspw. in Python und CPython bei der Arbeit zusehen.
Trotzdem viel Erfolg, weiß dass das aufwendig zu debuggen ist.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Klar. Das mit dem Handy war ja sein Argument, warum man denn das recompilieren machen sollte. Von wegen, für Desktops reicht die Performance, aber auf dem Handy muß das ganze mit wesentlich weniger CPU Leistung auskommen.

Leider erklärt es nicht wirklich, wie das im Detail funktioniert.

Wenn ich das richtig verstanden habe, geht es darum, das man Routinen im ROM durch nativen Programm code ersetzte, oder?

Also z.B. habe ich ja Unittests, die per Maschinen Code (Die Op-Code Liste) crc16, crc32 berechnet oder eine Division durchführen, siehe: https://github.com/jedie/DragonPy/blob/ ... program.py

Die Idee wäre also, diese Routinen zu erkennen und statt dann die Ops durcharbeiteten, das ganze direkt in Python (bzw. JS) zu machen. Das Ergebnis im "RAM" schreiben und an die RTS Addresse springen...

Schon interessant, aber nochmal um einiges Aufwendiger, denn man muß ja 100%tig den Zustand herstellen... ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Ja, im Prinzip schon.
Man transformiert einen Teil des Assembler Codes direkt in Python und lässt eval drüberlaufen.
Die Vorteile wären neben der Möglichkeit der Optimierung des Datenflusses, würde auch die VM den Programmcode optimieren können, PyPy kommt es sehr entgegen. Zudem interpretiert man weniger Assemblercode zur Laufzeit und hat damit bessere Latenz. Man müsste sehen wieviel schneller CPython das macht.

Rausgekramt aus einen alten 2009 Thread, Recompiling Brainfuck:

Code: Alles auswählen

import sys
import platform
 
def compile_brainfuck(src):
    program = ["def bf():", " import sys", " cells, ptr = [0] * 1000, 0"]
    indent = 1
    def emit(line): program.append(" " * indent + line)
    for s in src:
        if s == ">": emit("ptr += 1")
        elif s == "<": emit("ptr -= 1")
        elif s == "+": emit("cells[ptr] += 1")
        elif s == "-": emit("cells[ptr] -= 1")
        elif s == ".": emit("sys.stdout.write(chr(cells[ptr]))")
        elif s == ",": emit("cells[ptr] = ord(sys.stdin.read(1))")
        elif s == "[": emit("while cells[ptr]:"); indent += 1
        elif s == "]": emit("pass"); indent -= 1
    indent -= 1; emit("bf()")
    return "\n".join(program)
   
 
try:
        filehandler = open(sys.argv[1],"r")
except IndexError:
        print("wrong syntax: no source file")
        exit(-1)
       
code = ""
brainfucksymbols = ('+','-','>','<','.',',','[',']')
while True:
        line = filehandler.readline()
        if line == "":
                break
        for char in line:
                if char in brainfucksymbols:
                        code+=char
 
exec (compile_brainfuck(code))
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Routinen in native Code übersetzten, geht doch aber nur, wenn man die Routinen kennt. Also müßte man es doch für jedes ROM wieder neu anpassen. btw. man müßte eine riesige Sammlung an Routinen anlegen und dennoch kann es sein, das in einem anderen ROM btw. machinen code Programm keine bekannte Routine dabei ist...

coverage ist wirklich toll (s. http://www.python-forum.de/viewtopic.php?f=1&t=34236 )...
Nun sehe ich genau, welche Dinge ich noch übersprüfen muß ;)

Da wäre:
  • * ea relative byte
    * ea relative word
    * ASR
    * DAA
    * MUL
    * SEX
    * BRN
    * BVC
    * BVS
    * LSR memory
    * ROR memory
Das ist wirklich nicht viel!
Natürlich sind damit allerdings nicht alle Variationen abgedeckt...


Ganz neu, habe ich nun einen "BASIC Interpreter Interations"-Unittest... Also ich kann BASIC Programme in Unitests aufnehmen und prüfen, ob das entsprechende Ergebnis vom Interpreter zurück geliefert wird ;)

Ist noch ein wenig unschön zusammen gehackt, aber es funktioniert ;)

bsp:

Code: Alles auswählen

    def test_STR(self):
        self.periphery.add_to_input_queue(
            'A=0\r\n'
            '? "A="+STR$(A)\r\n'
        )
        op_call_count, cycles, output = self._run_until_OK(
            OK_count=2, max_ops=12000
        )
#         print op_call_count, cycles, output
        self.assertEqual(output,
            ['A=0\r\n', 'OK\r\n', '? "A="+STR$(A)\r\n', 'A= 0\r\n', 'OK\r\n']
        )
        self.assertEqual(op_call_count, 11229)
s. https://github.com/jedie/DragonPy/blob/ ... imple09.py

Ausgabe sieht dann z.B. so aus:
init machine...
6809 EXTENDED BASIC
(C) 1982 BY MICROSOFT

OK
done in 3Sec. it's 13657.44 cycles/sec. (current cycle: 47948)

______________________________________________________________________
test_STR (__main__.Test6809_BASIC_simple6809_Base) ...
A=0
OK
? "A="+STR$(A)
A= 0
OK
ok

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Vielleicht meinst du das gleiche und drückst dich missverständlich aus oder mir fehlt der Bierpegel. Ein Programm muss zum Start umgeschrieben werden, was je nach Programm mehr oder weniger gut funktionieren kann. Das geschieht bei allen Programmen, dein Recompiler ist das egal was die einzelnen Funktionen machen und vergisst die Infos nach der Beendigung auch wieder. Man muss auch nicht alle Segmente ersetzen, aber die die übersetzt sind, kürzen deine Schleife der Opcode Auswertung ab und zwar recht deutlich.
Würde man ein Sieb von Erast. in Assembler haben, müsste der bisherige Ansatz, jede Zeile Assembler in einer Schleife analysiert und ausgeführt werden(mit vielen vielen calls für die CPU Emulation) wieder und wieder. Der Recompiler spart dir im einfachsten Teil die Analyse, weil der passenden Python Code bereits vorliegt. Was man braucht ist also eine Lookup Tabelle, wo einzelne Segmente(je länger umso besser) vorliegen und wenn der pc dort ist, eval ausführt.

Ein Beispiel was das Prinzip verdeutlicht.

Code: Alles auswählen

mov ax, 1
mov cx, 511
schleife:
dec cx
mul ax, cx
jnc schleife 
Dein Interpreter würde 2 + 512x3 Mnemonic Auswertungen benötigen, ein primitiver Recompiler würde die Schleife 2 + 512. Ebenso muss der pc nicht bei jeden Befehl hochgezählt werden.

PS: Das obere Beispiel dient nur zum Verständnis, der Code macht was anderes weil 512! nicht ganz in ax reinpasst und 0 multipliziert der auch noch.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Verstehe was du meinst. An einfache Schleifen hatte ich nicht gedacht...
Ist das nicht das Prinzip von JIT-Compilern und das was PyPy Versucht?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Wobei man da bei 8-Bittern in der Regel bei realen Programmen schnell an eine Grenze kommt oder man zumindest beachten muss, das im RAM der Code nicht unveränderbar ist. Mit der CPU vom Dragon kenne ich mich nicht aus, aber auf dem 6502 & Co ist es üblich das Programme sich selber ändern in dem sie die Argumente von Maschinenbefehlen manipulieren. Manchmal auch Befehle selbst.

Und dann muss man noch irgendwie erkennen können, am besten im voraus, ob der entsprechende Code mehrfach ausgeführt wird, denn das Übersetzen in Python-Code ist ja auch nicht kostenlos.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Das stimmt auch. Bei Dragon ist das nicht anders... :)

Das was ich irgendwann mal machen werde, ist zumindest einen hook/callback einzubauen, den man an bestimmte Speicherstellen anpappen kann. In ersterlinie um den "RAM Test" zu beschleunigen.
Alle ROMs die ich bisher Probiert habe, testen erstmal wie viel Speicher vorhanden ist und das Byte für Byte. Das dauert ein wenig.

Das hat natürlich nichts mit Compilieren zu tun ;)

In gehackter Form habe ich z.Z. auskommentiert drin:

Code: Alles auswählen

    @opcode(# Branch always
        0x20, # BRA (relative)
        0x16, # LBRA (relative)
    )
    def instruction_BRA(self, opcode, ea):
        """
        Causes an unconditional branch.

        source code forms: BRA dd; LBRA DDDD

        CC bits "HNZVC": -----
        """
        # FIXME: remove speedup Simple6809 RAM test
#         if self.cfg.__class__.__name__ == "Simple6809Cfg":
#             if self.program_counter == 0xdb79 and ea == 0xdb6a: # RAM size test loop
# #                 msg = repr(["%x" % x for x in [self.program_counter, ea, m, self.index_x.get()]])
# #                 raise RuntimeError(msg)
#                 new_x = 0x7ffd
#                 new_ea = 0xdb79
#                 log.warn(
#                     "Speedup Simple6809 RAM test: Set X to $%x and goto $%x" % (
#                         new_x, new_ea
#                 ))
#                 self.index_x.set(new_x)
#                 self.program_counter = new_ea
#                 return

        log.info("$%x BRA branch to $%x \t| %s" % (
            self.program_counter, ea, self.cfg.mem_info.get_shortest(ea)
        ))
        self.program_counter = ea

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:Mal ein Update, nach den letzten Bugfixes...

Sieht dann so aus:

Code: Alles auswählen

6809 EXTENDED BASIC
(C) 1982 BY MICROSOFT

OK
10 FOR I=1 TO 3
20 PRINT STR$(I)+" DRAGONPY"
30 NEXT I
RUN
 .0/////909 DRAGONPY
 .0/////909 DRAGONPY
OK
So, ich habe endlich, endlich, endlich den wichtigen Bug gefunden. Nun sieht es so aus:
6809 EXTENDED BASIC
(C) 1982 BY MICROSOFT

OK
10 FOR I=1 TO 3
20 PRINT STR$(I)+" DRAGONPY"
30 NEXT I
RUN
1 DRAGONPY
2 DRAGONPY
3 DRAGONPY
OK
Dabei war der Bug mehr unter "Flüchtigkeitsfehler" zu verzeichnen. Ich habe Bei ADC schlicht vergessen, das Ergebniss zurück in das Register zu packen. Commit: https://github.com/jedie/DragonPy/commi ... c498c62262



Dennoch ist nicht alles richtig, denn:
?5/3
1.25
OK

?6/5
1.75
OK
...sollte eigentlich so aussehen:
?5/3
1.66666667
OK

?6/5
1.2
OK

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:...sollte eigentlich so aussehen:
?5/3
1.66666667
OK

?6/5
1.2
OK
Das ist nun auch so. Fehler gefunden: https://github.com/jedie/DragonPy/commi ... 4fac528353

Nun muß ich erstmal neue Fehler ausfindig machen. Vielleicht teste ich einfach die vorhandenen BASIC Befehle alle durch und gut.
Ich denke mit Optimierungen kann ich dann auch anfangen ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Hat dein Emulator auch Sound und wenn ja wie löst du das plattformunabhängig?
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Nein, kein Sound. Aktuell gibt es nur Text Aus-/Eingabe über eine Art "Serielle Schnittstelle", sonst nichts außer der CPU.

Aber: Es sieht so aus, als wenn die 6809 CPU nun einwandfrei Implementiert ist. Ich habe nun unittests mit quasi allen vorhanden BASIC Befehlen erstellt und die letzten Fehler korrigiert.

Nun ist es an der Zeit weiter zu denken. Als erstes will ich mal Aufräumen und ein wenig optimieren. Dann kann man sich mal überlegen, wie das mit der Peripherie weiter gehen soll.

Wobei ich eigentlich eh nicht vor hatte ein zweites XRoar zu programmieren. Als erstes würde ich super finden das Dragon ROM im Text modus nutzten zu können.


Existierende Lösungen nutzten meist PyGame...


Aus aktuellem Anlasse, mal ein Vergleich CPython vs. PyPy:
Ran 206 tests in 220.164s
Ran 206 tests in 33.932s

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Das dachte ich auch, nur sehe ich da nichts primitives. Den Umweg über Wav würde ich nicht gehen wollen.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

darktrym hat geschrieben:Das dachte ich auch, nur sehe ich da nichts primitives. Den Umweg über Wav würde ich nicht gehen wollen.
Naja, die Frage ist, was erzeugt der Emulator überhaupt intern?

Der Dragon kann IMHO eh nur Pipstöne von 0-255 und unterschiedlicher Länge.

Was willst du eigentlich emulieren? Scheinst ja auch an was zu arbeiten, oder?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Ich hatte hier einen NES Emulator, den ich ziemlich früh abgebrochen habe(nach der Portierung des CPU Kern und INES Format).
Die bekannten Impl. bspw. spyn fehlts auch an vielen u.A. am Sound.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Interessant, macht doch mal ein separaten Thread auf und erzähl ein wenig drüber... sourcen online?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Sourcen, ich glaube nicht das da was online ist, also nur lokales Repo, ich kann's mal hochladen, wen interessiert, soweit alles bsdl.
Irgendwo bei Impl. der PPU hab ich nicht mehr weitergemacht, kam viel dazwischen, altes Programmiererproblem.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

darktrym hat geschrieben:Das dachte ich auch, nur sehe ich da nichts primitives. Den Umweg über Wav würde ich nicht gehen wollen.
Einzig halbwegs taugliche plattformübergreifende Lib scheint libsdl zu sein. PyGame nutzt das auch, um die Lösung schmal zu halten, lohnt evtl. der Blick auf das Pythonmodul (https://bitbucket.org/marcusva/py-sdl2), welches anscheinend über ctypes an libsdl angebunden ist (habs nur überflogen).

Allerdings ist mir nicht klar, wie Du an Wav-Daten vorbeit kommen möchtest. Du musst dem Soundsink ja irgendwas "Welliges" vorsetzen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So... Ich glaube meine 6809 CPU Implementierung ist nun so gut wie Bug-frei...

Habe noch unittests mit dem Monitor ROM von sbc09 gemacht. Dort werden noch ein paar andere Instruktionen genutzt, die der BASIC Interpreter nicht nutzt.

Laut einem aktuellen coverage lauf, fehlen nur noch Tests für MUL und BVS. Alle anderen Ops sind vom Test abgedeckt.
Was noch fehlt sind die Implementierungen von RESET, RTI, SWI und SYNC... Aber die hatte ich bisher nicht gebraucht.

Durch das wegschmeißen von einigen Debugging-Code und ein wenig Code Generierung ist es auch um einiges schneller geworden.

Interessanterweise laufen die Unittests mit CPython in rund 8Sek. durch, wobei PyPy aber rund 10Sek. braucht...
jens hat geschrieben:* CPython native unter Windows: 235.000 cycles/sec (vorher 55.000 cycles/sec)
Simple6809 BASIC läuft auf meinem Rechner mit CPython aktuell mit rund 280.000 CPU cycles/sec. Also noch weit weg von den 895.000 der echten CPU...

PyPy unter Windows will gerade nicht:

Code: Alles auswählen

Traceback (most recent call last):
  File "D:\pypy-2.3.1-win32\lib_pypy\_tkinter\app.py", line 56, in PythonCmd
    with self.app._tcl_lock_released():
  File "D:\pypy-2.3.1-win32\lib-python\2.7\contextlib.py", line 17, in __enter__

    return self.gen.next()
  File "D:\pypy-2.3.1-win32\lib_pypy\_tkinter\app.py", line 164, in _tcl_lock_re
leased
    self._tcl_lock.release()
error: release unlocked lock



Wobei ich glaube die Werte sind so schlecht, weil die Aktuelle Lösung mit subprocess und socket Kommunikation zwischen CPU und Peripherie schlecht ist. Glaube das bremst extrem.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten