Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
ich versuche zur Zeit einen kleinen Emulator für den CHIP-8 Prozessor zu schreiben, weil ich gelesen habe das der besonders einfach sei. Jedoch stoße ich am Anfang schon auf ein paar Probleme. Es geht darum das ich nicht weiß welche Datentypen ich am besten verwende.
Ich habe dazu sogar ein Tutorial gefunden, aber da ich es nicht einfach abschreiben will, habe ich ein paar Tests gemacht und verstehe einige Sachen nicht.
In der Beschreibung von Wikipedia steht das die opcodes 2 bytes (16 bits) lang sind. Wieso benutzt er dann ein char[] memory Array, und nicht ein short[] memory Array?
Desweiteren verstehe ich nicht wieso
Code:
opcode = 0xA2 << 8 | 0xA2;
bei mir nicht funktioniert. Er will das ich es zu short caste, da dort ein Int rauskommt. Aber wenn ich richtig gerechnet habe müsste doch dort ein 16 bit opcode rauskommen, also in einem short speicherbar sein oder nicht?
Die "echten" Zahltypen sind in Java alle Vorzeichenbehaftet, (während die Opcodes vorzeichenlos sind?). Deswegen kann in einem short wahrscheinlich nicht der komplette benötigte Zahlenraum abgebildet werden. Ein char ist in Java vorzeichenlos. Deswegen hat der Kollege das wohl verwendet. Finde ich aber nicht so gut, weil char eigentlich ein Zeichentyp ist, in dem der Unicode-Wert des Zeichens gespeichert ist. Das kann man natürlich als Zahlwert interpretieren/missbrauchen. Aber bei allen Ausgaben kommt da nur Grütze raus, weil Java es eben als Zeichen ausgeben will.
Desweiteren verstehe ich nicht wieso opcode = 0xA2 << 8 | 0xA2; bei mir nicht funktioniert. Er will das ich es zu short caste, da dort ein Int rauskommt. Aber wenn ich richtig gerechnet habe müsste doch dort ein 16 bit opcode rauskommen, also in einem short speicherbar sein oder nicht?
Der Shift-Operator ist so definiert, dass er einen int liefert. Auch wenn das Ergebnis in einen short passen würde, weil der Wert klein genug ist, wandelt Java das trotzdem nicht selbst um. Denn das kann ja nicht für alle Fälle garantiert werden. Deswegen musst du explizit casten. Damit sagst Du Java: "Ich weiß, dass der Wert klein genug ist. Deswegen ist ein Cast sicher und führt nicht zu korrupten Daten."
ohne cast einfach so funktioniert! Das heißt er wird schon selber irgendwie auf die Größe achten oder gibt es einen anderen Grund warum es mit 7 klappt und 8 nicht?
Tatsache! Habe gerade ein bischen rumgespielt und der Compiler berechnet die Werte vor (bei Literalen kann er das ja) und ist wohl so nett, es zu akzeptieren, wenn das Ergebnis klein genug ist. Das klappt auch mit anderen Operatoren (z.B. *).
Wenn man allerdings eins der Literale durch einen Methodenaufruf ersetzt, geht es wieder nicht. Auf jeden Fall eine sehr interessante Feinheit, die Du da ansprichst.
Aber das finde ich komisch! << 8 Füllt doch einfach 8 Nullen auf oder nicht? Und hängt man an 8 bit 8 bit ran sind wir immer noch bei 16 bits. Oder habe ich irgendwas falsch verstanden?
Du baust hier aber nicht einfach zwei 8bit-Arrays zu einem längeren 16bit-Array zusammen. Wegen der Vorzeichenbehaftung des Datentyps kann ein Shifting zu einem Überlauf führen, weil effektiv nur 15bit für den Betrag der Zahl zur Verfügung stehen. Wenn das Shiften also dazu führt, dass eine 1 an die am meisten linke (und eben noch nicht darüber hinaus) Position geschoben wird, ist das schon ein überlauf, obwohl 8bit + 8bit 16bit ergeben.
Okay danke! Aber könntest du mir jetzt noch helfen und sagen wie ich das ganze löse? Einfach einen größeren Datentyp benutzen? Weil die opcodes sind in jedem Fall nur 2 Byte groß.
Ist ja ein netter Vorsatz nicht abschreiben zu wollen, aber in diesem Fall ein Irrtum. Char bietet sich hier nun mal als naheliegendster Datentyp an, verwende ihn einfach.
Ich hatte bloß eben Probleme mit char. Soll ich dann alles in char speichern? Also Memory, Stack, Registers? Ich habe es jetzt mal mit int versucht (siehe diese Seite) und es klappt ganz gut, bis auf diesen opcode: 3XNN Skips the next instruction if VX equals NN. Ich habe ihn so implementiert:
Java:
case 0x3000:
if(V[(opcode & 0x0F00)] == (opcode & 0x00FF))
pc += 4;
System.out.println("Opcode: Skips the next instruction if VX equals NN.");
break;
Java:
opcode = memory[pc] << 8 | memory[pc + 1];
switch(opcode & 0xF000) {
case 0x0000:
switch(opcode & 0x000F) {
case 0x0000:
// TODO:
pc += 2;
System.out.println("Opcode: Clears the screen.");
break;
case 0x000E:
// TODO:
pc += 2;
System.out.println("Opcode: Returns from a subroutine.");
break;
default:
System.out.println("Error: Unknow opcode: " + "0x" + Integer.toHexString(opcode));
}
break;
case 0x1000:
pc = opcode & 0x0FFF;
System.out.println("Opcode: Jumps to address NNN.");
break;
case 0x2000:
// TODO:
pc += 2;
System.out.println("Opcode: Calls subroutine at NNN.");
break;
case 0x3000:
if(V[(opcode & 0x0F00)] == (opcode & 0x00FF))
pc += 4;
System.out.println("Opcode: Skips the next instruction if VX equals NN.");
break;
case 0xA000:
I = opcode & 0x0FFF;
pc += 2;
System.out.println("Opcode: Sets I to the address NNN.");
break;
default:
System.out.println("Error: Unknow opcode: " + "0x" + Integer.toHexString(opcode));
}
> Soll ich dann alles in char speichern?
Für systemnahe Programme ist Java nun mal nicht erste Wahl. Da muss man mit Kompromissen leben. Das hier größte Problem besteht darin, das die Sprache die prozessortypischen Datentypen nicht kennen *kann*, weil plattformunabhäbgig.
Ja, ich denke das Problem ist das in Java alle Datentypen außer char signed sind.
Ich werde es mal versuchen alles in char zu speichern.
Wenn mir jetzt vielleicht noch jemand mit dem opcode helfen könnte wäre das echt super! Bin mir nämlich nicht sicher ob es nicht wegen den Datentypen oder wegen der Implementierung klappt.
pc bedeutet wohl Programmcounter. Geskipped werden 2 + 2 (also eine Instruktion). was aber wenn die Bedingung nicht zutrifft? Geht's dann erneut bei Skip weiter? Schalte die mal in einem else-Fall weiter (+2), damit die nächste Instruction ausgeführt wird.
Das ist was, was ich nicht bedacht habe, aber in diesem Fall nicht der Fehler sein kann, da ich in jedem Fall nur einen cycle ablaufen lasse. (Zu Testzwecken) Das Problem ist das ich eine Array Out Of Bounds Exception kriege.
Code:
V[(opcode & 0x0F00)]
V ist ein char-Array in dem die Register gespeichert sind. Wenn ich mir jetzt
Code:
opcode & 0x0F00
als binary ausgeben lasse kommt das raus : 10100000000. Das heißt die ersten 3 Stellen stimmen, ich habe nämlich die Zahl 5 als Ziel Register übergeben. Entweder brauche ich eine Methode um die letzten 8 bits loszuwerden, oder es gibt dafür eine andere Vorgehensweise?
Wie gross ist denn das Array? Evtl. nur so gross, wie es Opcodes gibt? Dann solltest du diese Opcodes vor dem Lesen aus dem Array isolieren und rechtsbündig shiften (z.B. >>> 8 bei 255 Opcodes).
Das Array ist 16 groß. In der CHIP-8 Beschreibung steht das es genau 16 Register gibt und 16 ist dabei die carry flag. Das heißt V1 - VF.
Vielleicht ist es gar nicht klar was ich machen will?
Ich habe den opcode z.B. 0x3123. Wenn es mit 3 anfängt muss ich gucken ob Register
Code:
0x1 == 0x23
ist.
Das heißt ich versuche die Zahl 1 auszulesen und das versuche ich mit
Code:
opcode & 0x0F00
. Kann das stimmen?
[EDIT]Ich glaube ich habe den Fehler jetzt gefunden, ich weiß nicht ob du das eben meintest aber so
Genau, das meinte ich. Evtl. schreibst jetzt noch [c]V[(opcode >>> 8) & 0xF][/c], dann hast du biem verknüpfen nicht mehr so 'ne grosse Zahl. Ausserdem, vorsicht! Deine Klammern passen nicht...[c]V[(opcode & 0x0F00) >>> 8][/c]
Okay sorry, ich habe wieder zu voreilig gepostet!
Ich hatte natürlich noch einen Fehler im code!
Um bei einem hex wert z.B. die 2. Stelle herauszufinden muss man
(opcode >>> 12) & 0xF für das höchste
(opcode >>> 8) & 0xF für das 2. höchste
(opcode >>> 4) & 0xF für das 2. niedrigste und
(opcode ) & 0xF für das niedrigste
nicht bequemer? Sollte sich die Reihenfolge der "Nibbles" mal ändern, braucht man sich um die &-Werte schon mal keine Gedanken mehr machen.