Beiträge anzeigen

Diese Sektion erlaubt es dir alle Beiträge dieses Mitglieds zu sehen. Beachte, dass du nur solche Beiträge sehen kannst, zu denen du auch Zugriffsrechte hast.


Nachrichten - Survari

Seiten: [1]
1
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 10. June 2021, 20:51 »
Okay. Diese Funktionsweise wirkt auf mich etwas schräg, aber ich denke ich habe es recht gut verstanden. Manchmal frage ich mich, warum man das nicht einfacher und damit verständlicher konzipiert hat, aber ich stecke nicht genug in der Materie drin, um das beurteilen zu können. Ich danke euch beiden für die Hilfe dabei, auf diese Lösung wäre ich nie gekommen!

Eine Frage hätte ich tatsächlich doch noch: Es ist etwas umständlich das Programm auf einem echten System zu testen, da die ganzen Emulatoren anders damit umgehen. Kann ich mit QEMU das UEFI-BIOS (=das VBIOS?) simulieren?
2
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 02. June 2021, 11:57 »
Zitat
Dein Code wird an die lineare Adresse 0x07C00 geladen, aber das BIOS kann daraus verschiedene Segment:Offset-Paare erzeugen. Du solltest das erstmal normalisieren.

Danke dir, das scheint das Problem gewesen zu sein! Aber kannst du das vielleicht etwas genauer erklären oder mich auf eine Seite verweisen, damit ich mich da mal näher zu belesen kann? Ich verstehe noch nicht genau, warum man das machen muss und was dahinter steckt.
3
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 27. May 2021, 08:06 »
Hi Svenska, danke für die Antwort!

Ich habe jetzt die Initialisierung des Stacks nach oben verschoben und den Interrupt mit 0xb8 entfernt. (Der war überflüssig, wahrscheinlich noch ein Überbleibsel von irgendetwas und ich habe vergessen es zu entfernen.) Läuft wieder in der Simulation, doch die PCs starten jetzt neu. (Aufgrund von Fehlern? Ich sehe sonst keinen Interrupt, der das rechtfertigen würde.)

So sieht der init-Abschnitt jetzt bei mir aus:

init:
    ; -- Stack initialisieren
    xor     ax, ax
    mov     ds, ax
    mov     ss, ax
    mov     sp, 0x9c00

    mov     ax, MEM_VIDEO   ; zeiger auf bildschirmspeicher
    mov     es, ax          ; ES=MEM_VIDEO

    mov     ax, 0x0003      ; 16 farben, 80x25 Zeichen
    int     0x10

    mov     ah, 1
    mov     ch, 0x26
    int     0x10
4
Lowlevel-Coding / Ein Spiel im Boot-Sektor
« am: 11. May 2021, 16:18 »
Spiele in den 512 Bytes des Boot-Sektors unterzubringen ist keine Neuheit, daher wollte ich auch mal etwas kleines probieren. Wie mit meinem letzten Projekt, bei dem ich hier um Rat gebeten habe, versuche ich etwas mehr über die Funktionsweise von Rechnern und x86-Assembly zu erfahren. Aktuell ist es erstmal mein Ziel, ein Rechteck mit Pfeiltasten über den Bildschirm zu bewegen.

Gesagt, getan. Das Programm läuft in QEMU super, Eingabe und Anzeige funktionieren wie gewollt. Jetzt das Problem: Es läuft nur auf QEMU (und Bochs). Sobald ich das Programm von einem USB-Stick auf meinem Rechner starte, macht es gar nichts mehr (Bildschirm bleibt leer, Cursor blinkt). Dabei ist mir ein möglicher Zusammenhang aufgefallen (bitte schlagt mich nicht, es folgen gefährliches Halbwissen und wahrscheinlich haufenweise falsche Annahmen): Es gibt Ausnahmen. Ich hatte viel Zeit das Programm an vielen Computern meiner Schule zu testen. Dabei ist mir aufgefallen, dass besonders Computer, die neuer waren, das Programm nicht richtig ausgeführt haben. Ich hatte die Überlegung, dass es möglicherweise am vom UEFI simulierten BIOS liegen könnte. Auf Rechnern ohne UEFI funktioniert es tatsächlich (zumindest gehe ich davon aus, dass die jeweiligen Rechner kein UEFI hatten, ich bin mir unsicher, wie genau ich das herausfinde). Soweit ich das beurteilen kann, würde ich außerdem eine Inkompatibilität aufgrund der Prozessorarchitektur ausschließen, da die meisten Computer auf x86/x64 laufen und sonst wahrscheinlich gar nichts passiert wäre (es gab Phasen, in denen Teile des Programmes auch auf den UEFI-Rechnern funktioniert haben, daher schließe ich das Problem mit den Architekturen aus).

Aktuell hat das Programm nette 118 Zeilen (NASM):
K_LEFT          equ 75
K_RIGHT         equ 77
K_UP            equ 72
K_DOWN          equ 80
MEM_VIDEO       equ 0xb800
SCREEN_WIDTH    equ 80
SCREEN_HEIGHT   equ 25
TICK_LENGTH     equ 4

org 0x7C00
bits 16

init:
    mov     ax, 0x0002      ; 16 farben, 80x25 Zeichen
    int     0x10

    ; -- Stack initialisieren
    xor     ax, ax
    mov     ds, ax
    mov     ss, ax
    mov     sp, 0x9c00

    mov     ax, MEM_VIDEO   ; zeiger auf bildschirmspeicher
    mov     es, ax          ; ES=MEM_VIDEO
    mov     al, 0x03
    int     0x10

    mov     ah, 1
    mov     ch, 0x26
    int     0x10

main:
    ; -- Bildschirm löschen und neu zeichnen
    call    clear_screen
    call    draw

    ; -- Eingabe und zurück zum Anfang springen
    call    input
    jmp     main

input:     ; steuerung
    xor     ah, ah
    int     0x16            ; tasteneingabe

    cmp     ah, K_RIGHT     ; Pfeiltaste nach Rechts
    je     .right_arrow

    cmp     ah, K_LEFT      ; Pfeiltaste nach Links
    je     .left_arrow

    cmp     ah, K_DOWN      ; Pfeiltaste nach Unten
    je     .down_arrow

    cmp     ah, K_UP        ; Pfeiltaste nach Oben
    je      .up_arrow

    jmp     .end

    .right_arrow:
        cmp     word [x_pos], word SCREEN_WIDTH-2
        jg      .end                    ; x_pos > SCREEN_WIDTH-2, keine veränderänderung
        add word [x_pos], 1
        jmp     .end

    .left_arrow:
        cmp     word [x_pos], word 1
        jl      .end                    ; x_pos < 1, keine veränderung
        sub     word [x_pos], 1
        jmp .end

    .down_arrow:
        cmp     word [y_pos], word SCREEN_HEIGHT-2
        jg      .end                    ; y_pos > SCREEN_HEIGHT-2, keine veränderung
        add     word [y_pos], 1
        jmp .end

    .up_arrow:
        cmp     word [y_pos], word 1
        jl      .end                    ; y_pos < 1, keine veränderung
        sub     word [y_pos], 1
        jmp .end

    .end:
        ret

clear_screen:
    ; -- Ganzen Bildschirm mit Schwarz füllen
    xor     di, di
    mov     cx, 0x07d0
    mov     ax, 0x0000
    rep     stosw
    ret

draw: ; Block an der Stelle aus [x_pos] und [y_pos] zeichnen
    ; -- Umwandeln der Koordinaten in Index für den Bildschirmspeicher, Ergebnis in dx
    mov     ax, word [y_pos]
    mov     bx, 160
    mul     bx
    mov     dx, ax

    mov     ax, word [x_pos]
    add     dx, ax
    add     dx, ax

    ; -- Datenindex setzen und zeichnen
    mov     di, dx
    mov     ax, 0xa020
    stosw
    ret

done:
    jmp     $           ; Endlosschleife am Ende (was eigentlich nie erreicht wird)

x_pos: times 10 dw 0
y_pos: times 10 dw 0

times 510-($-$$) db 0
dw 0xaa55            ; Signatur für's BIOS

Meine Frage dazu wäre dann wahrscheinlich klar: Warum läuft das Programm nicht? Gibt es Unterschiede am vom UEFI simulierten BIOS? Liegt es vielleicht an etwas völlig Anderem? Ich bin für jede Hilfe und Erklärung dankbar!
5
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 15. June 2020, 17:06 »
Zitat
Genau das meinte ich mit "Lochstreifenleser sind ungenau". :-) Wenn du ein Standardformat benutzt, dann kannst du auch Ausschau nach einem alten Lesegerät halten und damit testen. Vor langer Zeit habe ich ein Youtube-Video gefunden, was einen (vermutlich selbst gebauten) Leser zeigt: https://www.youtube.com/watch?v=uZnuu18FtQk.

Danke dir, das habe ich aber tatsächlich schon gesehen. Hab recht viel recherchiert zu verschiedenen Techniken, wie man ein Lesegerät bauen kann. Am Ende wollte ich dann eins mit Lichtsensoren bauen. Mein neuster Versuch klappt, das aktuelle Gerät liest zuverlässig ein. Nur wenn es zu schnell wird, gibt's Fehler. Und für den Fall der Fälle generiere ich für jeden Lochstreifen, den ich stanze, einen Hash mit 8 oder 16 Zeichen. Die kann man schnell auf den Streifen schreiben und leicht überprüfen. Wenn es Unterschiede zwischen dem Hash der Originaldatei und dem des gelesenen Code gibt, weiß ich ja, dass ich zu schnell beim Lesen war :roll:.

Edit: Habe gerade den Compiler und den Assembler fertig bekommen. Jetzt fehlen nur noch die restlichen Instruktionen im Emulator und dann bin ich tatsächlich fertig mit dem Projekt. Läuft bisher tatsächlich echt gut, hätte ich nicht gedacht. Besonders beim Compiler hatte ich erst Angst, dass er mir extrem viel Zeit kostet. Aber habe ihn an zwei Tagen fertig bekommen. Nachdem der Emulator fertig ist, baue ich ein kleines Nummern-Rate-Spiel. Bin gespannt, wie und ob das hinhaut.
6
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 10. June 2020, 18:37 »
Zitat
Kurz: Das ist alles wesentlich komplizierter als du dir das vorstellst, weil x86 einfach kompliziert ist.

Da hast du recht. Ich denke mal, dass ich das einfach nicht umsetze. Der ganze Aufwand für das kleine Projekt ist schon groß genug, ich muss es mir nicht noch komplizierter machen :-D .

Zitat
Garnicht. :-) Optimierung ist nicht Aufgabe des Assemblers, darum sind Assembler relativ einfach zu programmieren. Optimierung findet im Compiler statt, und deswegen sind Compiler kompliziert. Darum erstmal weglassen und per Hand optimieren.

Super. Dann habe ich dich nur falsch verstanden.

Zitat
Das Indexloch sagt dem Lesegerät, dass hier Löcher sind. Aber es trägt selbst keine weitere Information (schließlich ist es immer gelocht). Ein Paritätsbit gibt an, ob die Summe aller Einsen gerade oder ungerade ist. Wenn der Leser also z.B. "10101010" sieht, aber das Paritätsbit auf "ungerade" steht, dann weißt du, dass du einen Lesefehler hattest.

Aber wenn das Paritätsbit auch falsch eingelesen wird, ist das doch eh egal, oder nicht? Oder meinst du, dass man per Hand nachschauen soll, ob nun ein Paritätsbit richtig eingelesen wurde, oder nicht?

Zitat
Naja, wenn du nicht den Anspruch hast, dass es funktionieren soll...

Ich bin verwirrt. Du meintest, dass ich mich dafür entscheiden könnte, entweder jeden Befehl gleich lang zu machen, oder unterschiedlich lange Befehle zu haben und hast dann Vor- und Nachteile aufgezählt. Ich empfand die Nachteile (kompliziert und langsam) nicht als überwiegend und habe mich daher zur Methode mit den unterschiedlich langen Befehlen entschieden. Warum sollte es jetzt nicht funktionieren?

Zitat
Frage: Wenn jeder Befehl 8 Bit hat, und du davon 8 Bit für den Opcode reservierst, wie würdest du dann "schreibe die Zahl 42 in Register 3" codieren?

Argh, ich habe mich mal wieder falsch ausgedrückt. 8bit soll nur der Opcode für die eigentliche Instruktion (<- ist das jetzt das richtige Fachwort? Ich bin langsam verwirrt, wenn für mich "Instruktion", "Anweisung" und "Befehl" fast das gleiche bedeuten und doch in verschiedenen Kontexten anders gedeutet werden können), ohne Argumente sein, also nicht der komplette Befehl. Daher komme ich auch auf maximal 255 unterschiedliche Instruktionen (ohne Argumente). (Wenn ich also 50 verschiedene Mov-Befehle habe, die unterschiedliche Dinge tun, habe ich nur noch 205 Instruktionen frei, die ich belegen kann, bevor ich meine 8bit verbraucht habe.)

Zitat
Ein Lochstreifenlesegerät ist ein serielles Gerät (wie ein Modem). Da kommen einfach Bytes rein, die schreibst du in ein Array und wenn du fertig bist, lässt du den Emulator mit dem Array loslaufen. Als Bytecode-Interpreter kannst du was fertiges nehmen, z.B. das hier. Da musst du nur die Funktion load_program() von COM10 lesen lassen und fertig. :-)
Der Assembler dafür ist in Python geschrieben und erzeugt dir die Binärdaten, die du dann einfach byteweise nacheinander in den Lochstreifen stanzen musst.

Das ist cool, aber ich finde die Idee vom eigenen Emulator und Befehlssatz gerade so spannend, dass ich gerne mal die Erfahrung machen möchte, beides zu erstellen. Ich bin gerade super motiviert dafür und diese Motivation kann ich echt gebrauchen.

Zitat
Hast du denn schon einen Stanzer und ein Lesegerät?

Der Stanzer ist da, das Lesegerät ist noch in Arbeit. Ich baue es selbst, daher habe ich noch Probleme mit Ungenauigkeit beim Einlesen. Aber ich muss erst mal verschiedene Dinge ausprobieren, optimieren usw. Damit habe ich wahrscheinlich noch etwas zu tun, daher habe ich währenddessen genug Zeit mich mit dem Befehlssatz hier auseinanderzusetzen :wink:.
7
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 09. June 2020, 17:54 »
Zitat
Jeder AVR-Befehl besteht aus 16 Bits (=1 Flash-Word, bzw. 2 Bytes). Jeder ARM-Befehl hat 32 Bits (=1 Word). Ein 8086-Befehl hat mindestens 1 Byte (sowas wie "CLI") und maximal 6 Byte (sowas wie "MOV [BX+0x85], 0xAC24"). Dazu kommen dann noch eventuelle Präfixe (wie "LOCK", "ES:" oder "REP").

Ah, okay! Das bedeutet auch, dass "MOV [BX+0x85], 0xAX24" ein eigener Befehl ist und das [BX+0x85] nicht vom Assembler umgewandelt, sondern direkt zum Befehl gehört? Dieser spezielle Mov-Befehl beinhaltet also das Addieren der ersten beiden Argumente? Dann hätte sich meine Ursprunsfrage geklärt.

Zitat
Als Beispiel: Ein 8086 bindet den Stack ziemlich fest an das SP-Register (PUSH/POP arbeiten damit). Auf ARM hingegen kannst du (fast) jedes Register als Stackpointer nehmen, weil das keine Sonderbefehle sind, sondern Adressierungsmodi (post-increment bzw. pre-decrement). Das sind aber keine Stackmaschinen. Wenn du z.B. einen FORTH-Interpreter bauen willst (das ist eine stackbasierte Programmiersprache), dann willst du eigentlich zwei Stacks in Hardware unterstützen.

Nehme ich so (wie beim 8086, weil ein Stack ja ziemlich nützlich sein kann), da sich meine ursprüngliche Frage, die mein einziger Grund für das Einbauen einer Stackmaschine war, sich ja geklärt hat. Aber ich denke, dass ich ihn dann auch nur als Zwischenspeicher nutzen werde (also keine Extra-Befehle, die nur mit dem Stack arbeiten, außer es gibt einen guten Grund dafür). Dann reichen push und pop als Befehle.

Zitat
Genau das ist die Idee. Der Prozessor betrachtet die alle als vollkommen unterschiedliche Befehle, aber für den Programmierer sind das alles MOVs. Der Assembler muss dann raussuchen, welche Variante er nimmt.

Gut, das habe ich dann verstanden. Das ist ziemlich nützlich.

Zitat
Du wirst ihn aber wahrscheinlich trotzdem haben, auch wenn das nur verschiedene Funktionen im gleichen Programm sind. Der Punkt ist, dass ein Assembler ein recht einfaches Programm ist (eben weil er einfach nur 1:1 von Assemblerbefehlen in Maschinenbefehle umwandelt), während ein Compiler - oder dein Transformator - beliebig komplex werden kann. Stichwort "Optimierung".

Im Übrigen erzeugst du keine "Opcodes", sondern "Befehle" (Instruktionen). Der Opcode sind nur ein paar Bits davon. Wieviele und welche, legst du fest.

Da hast du natürlich recht. Und tatsächlich überlege ich doch einen Assembler zu bauen, der meinen Code 1:1 übersetzt (dann von Compiler -> Assembler -> Computersprache).  Doch in welcher Weise hilft dies bei der Optimierung? (Kurz dazu: Ich muss bei "Optimierung" im Bezug auf Programmiersprachen immer an die Optimierung und Vereinfachungen von Compilern wie die GCC denken, bei denen die Optimierung des Programmcodes ja echt gut ist. Falls du was anderes meinst, lass es mich wissen.)

Zitat
Lochstreifen sind nicht die zuverlässigste Technologie - nicht jetzt, und auch nicht vor 50 Jahren. Übliche Lochstreifensysteme sind daher 7 Bit, und das 8. Bit ist ein Paritätsbit, damit du wenigstens weißt, wenn dein Lesegerät falsch gelesen hat.

Ich hab noch ein Indexloch. Dann kann ich doch das 8. Bit als Wert noch dazu nehmen. Ist das das was du meintest? Nicht, dass ich wieder an deiner Aussage vorbeirede :-D .

Zitat
Das ist das, was ich oben meinte: Du kannst einen Befehlssatz bauen, wo jeder Befehl die gleiche Anzahl Bits hat (und das muss auch nicht ein Lochstreifenelement sein, das können auch immer zwei sein). Oder du baust einen Befehlssatz, wo verschiedene Befehle unterschiedlich viele Bits haben. Der Vorteil ist, dass du deinen Befehlssatz komplexer und damit ausdrucksstärker machen kannst. Der Nachteil ist, dass deine Hardware wesentlich komplizierter wird. Einen Teil könntest du ausgleichen, indem du geschickt codierst.

Komplizierter und langsamer sind für mich kein Problem, da es ja, wie gesagt, nicht perfekt sein muss :wink:.

Zitat
Jaein. Was du meinst, sind üblicherweise verschiedene Opcodes ("MOV IMMEDIATE INTO REGISTER" bzw. "MOV INDIRECT INTO REGISTER"). Was ich meine ist, welche dieser Opcodes existieren.

Zitat
Du beschreibst die Syntax der Assemblersprache im Editor auf dem Entwicklungssytem. Das hat nur wenig damit zu tun, wie die CPU die einzelnen Bits interpretiert, die da rauskommen. Will sagen: Nein, die CPU sieht weder "[]" noch "@" noch "0x", sondern die sieht nur Bits.

Gut, dann habe ich deine Frage nur falsch verstanden. Ich habe überlegt, dass wie bei meinem 5byte-MOV-Befehl (keine Sorge, der wird so auf gar keinen Fall eingebaut), einfach ein Argument anzugeben, das aussagt, ob das nächste Byte ein Immediate oder eine Speicheradresse ist. Also sowas wie "mov ax, <t>, <n>", wobei t angibt, ob n eine Speicheradresse oder ein Immediate ist. Je nachdem wie viele Befehle ich am Ende (im Allgemeinen) habe und je nachdem, ob sich es lohnt, könnte ich auch noch jeweils die in einzelne Instruktionen aufteilen, die dann t weglassen und n entweder als Immediate oder als Speicheradresse nehmen (wie du es beschrieben hast mit "MOV IMMEDIATE INTO REGISTER" bzw. "MOV INDIRECT INTO REGISTER"). Das muss ich dann aber entscheiden, wenn ich weiter mit der Planung bin. Ich muss mir dazu wahrscheinlich mehrere Befehlssätze als Referenzen suchen, um zu entscheiden, welche Instruktionen ein CPU noch bräuchte und ob ich dann mit 8bit noch genug Möglichkeiten habe, so viele hinzuzufügen (nun gut, 255 mögliche Befehle sollten genug sein :-D ).

Zitat
Richtig. Aber das nennt man nicht "dann mache ich viele neue Opcodes", sondern das nennt man "Operanden".

Beispiel: Nehmen wir einen Prozessor mit 4 Registern (A, B, C und D), und 16 Befehlen insgesamt. Die ersten 4 Bits sind der Opcode, die nächsten 2 Bits sind der Zieloperand, die nächsten 2 Bits sind der Quelloperand. Macht 8 Bits (=1 Byte) pro Befehl. Nehmen wir als XOR den Opcode 4, dann ergeben sich die folgenden Kombinationen:

0x40 = 0100 0000 = XOR A,A
0x41 = 0100 0001 = XOR A,B
0x42 = 0100 0010 = XOR A,C
0x43 = 0100 0011 = XOR A,D
0x44 = 0100 0100 = XOR B,A
...
0x47 = 0100 0111 = XOR B,D
0x48 = 0100 1000 = XOR C,A
...
0x4F = 0100 1111 = XOR D,D

Für ADD mit Opcode 8 sieht das dann so aus:

0x80 = 1000 0000 = ADD A,A
0x81 = 1000 0001 = ADD A,B
...
0x8F = 1000 1111 = ADD D,D

Ich hoffe, du siehst da ein System drin.

Ja, das System ergibt Sinn. Danke!

Zitat
Im Übrigen hast du noch ein Problem komplett vernachlässigt: Wenn du direkt Code vom Lochstreifen ausführen willst - wie willst du mit Sprüngen (und damit auch Schleifen) umgehen? Da folgt auf einen Befehl ja nicht "der nächste" Befehl, sondern irgendein anderer. Alle Computer, von denen ich weiß und die mit Lochstreifen umgehen können, lesen den Lochstreifen immer komplett in den Arbeitsspeicher ein und führen den dann von dort aus. Und wenn der Code erstmal im Speicher ist, ist es ja auch egal, ob er nun via Lochstreifen, Festplatte oder Funk da reingekommen ist. Du kannst auf Sprünge und sowas auch verzichten, aber dann ist das Teil nicht mehr turingmächtig, d.h. du kannst keine beliebigen Programme mehr dafür bauen.

Ich führe den Code nicht direkt vom Lochstreifen aus. Ich lese den Lochstreifen erst ein und speichere den Code, wie alle anderen. Habe ich was anderes gesagt? Vielleicht habe ich mich falsch ausgedrückt.

Zitat
Und dann kommt noch eine Frage: Was willst du eigentlich für Speicher nehmen? Oder soll dein System sowohl Lochstreifenstanzer und Lochstreifenleser haben, und du mischst Code und Daten auf dem Lochstreifen? Das verbraucht eine Menge Papier (und produziert Unmengen an Konfetti - und Lochstreifenkonfetti ist eklig aufzuräumen).

Das soll alles digital im Emulator ablaufen. Ich will am Ende nur den Code vom Lochstreifen lesen. Der Code wird dem Emulator übergeben, dann gehts von da aus weiter.
8
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 04. June 2020, 23:45 »
Hi Svenska! Bei deiner Beschreibung sind mir einige Sachen klar geworden. Aber es haben sich mir auch einige neue Fragen gestellt. Doch kurz vorab würde ich gerne noch etwas klären: Ich nehme mir bei meinen Projekten meist nur die grundlegenden Ideen von bereits bestehenden Prinzipien. Von da aus reiße ich gerne aus verschiedenen Konventionen aus. Ich probiere sehr gerne rum, daher wird das auch kein hundertprozentig akkurater Emulator :roll:. (Zum Teil drücke ich mich auch einfach davor, einen richtigen Emulator zu schreiben, habe halt auf meine Weise dabei mehr Spaß :-D ). Jetzt aber zu den Fragen:

Zitat
Eine übliche Klassifikation unterscheidet zwischen RISC und CISC. Was das im Detail bedeutet, darüber kann man lange streiten, aber für die Diskussion hier vereinfache ich das mal so: In einem RISC-Prozessor hat jeder Befehl die gleiche Anzahl an Bits, in einem CISC-Prozessor nicht.

Könntest du dafür vielleicht ein Beispiele bringen (zu Befehlen mit unterschiedlich vielen Bits)? Oder meint die Anzahl der Bits eines Befehls "den Namen" des Befehls (ich meine jetzt nicht wortwörtlich den Namen wie z.B. "mov", sondern die Instruktion dahinter (<- da hast du mich ein kleines bisschen verwirrt, ist das nicht der Opcode?), z.B. (ausgedacht:) 0010 (ein 4-bit Befehl) und 01010010 (ein 8-bit Befehl))?

Zitat
Eine weitere Klassifikation unterscheidet zwischen Stack- und Registermaschinen. Was du beschreibst, ist eindeutig eine Stackmaschine, da Operanden auf den Stack gelegt werden und von dort verarbeitet werden.

Ah, ja, deshalb wollte ich das am Anfang kurz klarstellen. Ich dachte mir tatsächlich, dass ich beide Varianten vereine. Wenn man "add" also ohne Parameter angibt (hab das im Beispiel glaube ich "adds" gennant, also dem einen eigenen Namen gegeben), wird der Stack als Zwischenspeicher usw. benutzt. Wenn man aber Parameter angibt, verhalten sich die Befehle anders. Das kann zwar für andere zu Verwirrung führen, aber für mich ist das so angenehm. Und am Ende bin ich ja der einzige der das Programm benutzt. Und wenn einem das nicht gefällt, muss man den Befehl "adds" ja nicht nutzen ;).

Zitat
Assembler als Programmiersprache ist normalerweise eine direkte Abbildung der Fähigkeiten einer CPU. Da es so kaum Architekturen gibt, die dein "mov 2,4" direkt abbilden können, ist das fast nie ein gültiger Befehl.

Deswegen war das auch nur Pseudocode. Wir rennen gerade komplett am Thema und der eigentlichen Frage vorbei. Das ist aber kein Problem, ich lerne dabei ja viel neues. Eigentlich hatte ich vor den Mov-Befehl so ähnlich aufzubauen:

syntax:  mov <t1> <w1> <t2> <w2>
bsp.:    mov 1 1 3 2  # al = 2

(Alle Werte werden in 8bit-Blöcke aufgeteilt: t1, t2, w1 und w2 sind allesamt 8bit lang, genau wie der mov Befehl an sich) t1/t2 würde dabei dafür stehen, welche Art von Daten als Nächstes kommen. w1/w2 sind die Werte. Wenn t1 0 ist, muss w1 eine Speicheradresse sein. Wenn t1 1 ist, muss w1 die ID eines Registers (ich würde denen dann halt IDs geben; hier wäre ax jetzt 1) beinhalten. Ist t2 1 -> w2=Register-ID; 2 -> Speicheradresse; 3 -> Zahl in den nächsten 8bit. Das Beispiel sieht also die 1, nimmt die nächste 1 als Register-ID (al), sieht die 3 und läd die darauf folgenden 8 bit in Register al. Aber das ist wahrscheinlich zu kompliziert, oder? Ich muss mir eh erstmal ein paar Befehlssätze anschauen und wie die das Problem lösen. Aber gerne würde ich trotzdem eine Meinung dazu hören.

Zitat
Einfache Assembler bestehen z.B. aus einer Liste von Mustern, und die Eingangsdaten werden damit abgeglichen. Für einen Befehl (z.B. "mov ax, bx") wird dann ein passendes Muster gefunden (z.B. "mov <reg16>,<reg16>"), dann werden die Parameter in das Muster eingesetzt ("mov <reg16>,<reg16>" ist "1000100111SSSDDD", S=Quellregister, D=Zielregister, also generiert "mov ax,bx" ein "0x89 0xD8"). Wenn es kein passendes Muster gibt, dann war der Befehl ungültig.

(A1) Das nehme ich sehr gerne als Alternative! Das bedeutet dann, dass es verschiedene Mov-Befehle gibt, oder? Einer, der Zahlen in Register verschiebt; einer, der Register in Register schiebt; usw... Die haben dann alle einen unterschiedlichen Opcode, oder? Wenn ich das richtig verstanden habe, hört sich das für mich super lohnend an.

Zitat
Was du als Pseudoassembler bezeichnest, geht eher in Richtung einer Hochsprache, weil du den Code noch transformierst, also grundlegend veränderst ("mov 2, 4" ist etwas anderes als "push 4; push 2; mov"). Das machen einfache Assembler nicht (Makroassembler lasse ich hier mal außen vor).

Das finde ich gar nicht schlimm. Am Ende soll der Compiler (da es ja anscheinend kein Assembler ist) meinen Code einfach nur in Opcodes umwandeln, die ich in den Lochstreifen stanze. Den Umweg über einen "wirklichen" Assembler lohnt sich meiner Meinung nach dann nicht wirklich. (Wahrscheinlich werde ich das Teil trotzdem weiter "Assembler" nennen, einfach weil ich es meistens vergessen werde "Compiler" zu nennen :roll:. Ich versuche mich dran zu erinnern.)

Zitat
Achte dabei mal auf folgende Fragen:
- Wie werden die Befehle codiert? Wie viele Bytes hat ein Befehl?
- Wie werden "immediates" (im Befehl hingeschriebene Zahlen, z.B. "mov r4, 4") codiert?
- Welche Adressierungsmodi gibt es, und wie sind sie beschränkt?

Die erste Frage kann ich schon beantworten: 8bit. Einfach, weil mein Lochstreifen 8 Löcher hat und ich jeden Befehl in einer Zeile haben möchte. Es ist für mich praktikabler und übersichtlicher, auch wenn es größer ist. Oder muss der gesamte Syntax eine feste größe haben? Muss also "push" (den ich jetzt beispielsweise 2 Bytes groß machen würde. Das 1. Byte wäre für push an sich und das 2. Bytes für das Argument, was zu pushen ist.) genau so groß sein wie "mov" (dem ich dann jetzt Beispielsweise einfach mal 3 Bytes Platz geben würde. Das 1. Byte für mov an sich, das 2. Byte für das Ziel und das 3. Byte für den Wert.)? (Beide Befehle waren reine Beispiele, mir ist klar, dass die Befehle am Ende anders aussehen und ein mov-Befehl auch nur 2 Bytes oder 1 Byte groß sein kann (siehe (A2) weiter unten). Ich wollte nur versuchen, das Prinzip mit der Länge eines Befehls, so wie ich es verstanden habe, auszudrücken.)

Zur zweiten Frage habe ich eine Gegenfrage: Meinst du mit der Codierung von Zahlen deren System (Hex/Dez/Bin, würde ich alle einbauen per 0x..., 0b... und 0d..., wobei Dezimal ohne Präfix Standard wäre), oder die Bit der Zahlen? Auch hier wäre die Antwort 8bit, wegen genannten Gründen. Oder meinst du etwas anderes?

Und zu den Adressierungsmodi muss ich mir noch echt Gedanken machen... Vielleicht entscheide ich das spontan nach Gefühl. Oder sollte ich mich an ein einziges Prinzip halten? Addressierungsmodi habe ich jetzt so verstanden, dass sie Aussagen, wie die Operanden interpretiert werden: Also ob eine gegebene Zahl bei z.B. "mov ax, 1", dass entweder 1 in ax geladen wird, oder ob die 1 dort für eine Speicheradresse steht. Und auch hier würde ich beides kombinieren. In NASM wird das doch mit [] geregelt. Ich bin auf die Darstellung mit @ gestoßen. Dann würde ich alle Immediates (<- so heißen doch konstante Zahlen im Programmcode?), die ohne @ stehen direkt als deren Wert nehmen. Wenn dann ein @ dazukommt, ist eine Speicheradresse gemeint. Also "mov ax, 1" setzt ax auf 1, während "mov ax, @1", ax auf die ersten 16bit im Speicher setzt (weil ax ja 16bit groß ist, wäre das gut, oder?). Apropos würde ich dazu gerne noch was sagen: Ich würde den Speicher auch in 8bit-Blöcke aufteilen. Man soll also nicht auf kleinere Einheiten zugreifen können. Dadurch würden also alle Speicheradressen als Vielfache von 8 umgerechnet werden. Schreibt man also "mov al, @2", würden der 2. 8bit-Block im Speicher in al (nicht 16bit sondern 8bit, weil al ja nur 8bit groß ist. Oder?) geladen werden. Wie findest du das?

(A2) Aber wieder habe ich etwas auf der Recherche (zu Adressierungsmodi) dazugelernt: Man kann ja den Programmcode kürzen, indem man häufige Instruktionen in einem Programmcode zusammenführt. In Kombination von deiner Erklärung zu (A1) könnte man aus Instruktionen wie "mov ax, bx" für jede Kombination von Registern einzelne Opcodes anfertigen, oder? Wäre natürlich vielleicht etwas zu viel, aber man könnte dann ja vielleicht Unterschiedliche auslegungen des Mov-Befehls machen, einer, der zum Beispiel gegebene Werte nach ax verschiebt, der Register nach ax verschiebt usw. Dann könnte ich (wenn "mov ax, bx" in Opcodes "0x01 0x02 0x03" (0x01=mov reg16 zu reg16; 0x02=ax; 0x03=bx) wäre) einfach ein Argument weglassen (z.B. wird dann aus "mov ax, bx" "0x04 0x03" (0x04=mov reg16 zu ax), ein Byte weniger, das gestanzt werden muss).

Wie du siehst, hast du es mit einem Dulli auf diesem Fachgebiet zu tun :-D . Ich hoffe, dass ich dich mit meinen Ideen und Fragen nicht vergraule. Ich denke gerne über alle meine Positionen nochmal nach. Und ich will mich nochmal für diesen Post entschuldigen, da er recht durcheinander ist und nur teilweise das Konzept, dass ich mir vorstelle, wirklich aufzeigen kann. Ich hoffe, dass du mir trotzdem Feedback dazu geben kannst.
9
Lowlevel-Coding / Lochstreifen und Opcodes
« am: 02. June 2020, 19:22 »
Hallo :). Lange ist es her, seit ich auf dieser Seite mal vorbeigeschaut habe. Derzeit grabe ich in den Tiefen der Computergeschichte und bin auf die Lochstreifen gestoßen. Irgendwie haben mich die so fasziniert, dass ich auf die Idee gekommen bin, einfach mal selber welche zu machen und ein Lesegerät dafür zu bauen: Und dann ging es weiter... Natürlich hab ich mir die Frage gestellt, was ich denn eigentlich mit den Lochstreifen machen will und dachte da dann, man könnte sich ja mal an 'ner Prozessor-Emulation wagen und dann dafür Programme schreiben :roll: .

Ich will später einen Assembler bauen, der dann kleine Programme kompiliert und mir zeigt, wo ich welche Löcher stanzen muss :-D . Jetzt aber zum eigentlichen Problem: Ich habe Probleme beim Verstehen von Opcodes. Und mehr Lowlevel als per Hand Opcodes schreiben geht wohl nicht! Da muss ich mich doch an dieses Forum hier wenden :wink: .

Mal ein Beispiel an einem Pseudoassembler:

mov 2, 4 # speicher an stelle 2 auf 4 setzen
Die obige Zeile kann ich auch in Pseudo-Opcodes für meinen Emulator übersetzen (hier einfach mal Beispielsweise wären die in Dezimal dann "1 2 4" oder so). Das Problem ist aber so ein Beispiel:

mov 1, [1+1] # wert von speicherstelle 2 (= 1+1) nach speicherstelle 1
So ähnlich kann man das ja in NASM machen. Mein Problem dabei: Wie stelle ich das als Opcode dar? Einen Ansatz habe ich bereits: Ich habe bereits einige Pseudoassembler und Programmiersprachen geschrieben (nur recht kleine). In einer handhabe ich das so, dass alles, was in [] steht, als mathematischer Ausdruck gilt ([] haben da eine andere Bedeutung als die [] in NASM) und vom Compiler per einfachem Shunting-Yard-Algorithmus in neue Befehle übersetzt wird, die dann den Stack als Zwischenspeicher der Ergebnisse nutzen. Ach, am Besten ich zeig einfach ein Beispiel:

println [ 2*4+1 ]

Würde übersetzt werden (auch wieder nur Pseudo-Code):

push 2 # 2 auf den stack
push 4 # 4 auf den stack
mul # 2 obersten werte vom stack nehmen, multiplizieren, ergebnis auf den stack
push 1 # 1 auf den stack
add # 2 obersten werte vom stack nehmen, addieren, ergebnis auf den stack
println $!  # $! ist hier einfach ein operator, der den obersten wert vom stack löscht und ihm den befehl übergibt. alle []-ausdrücke werden durch $! ersetzt, da die Ergenisse immer auf dem Stack liegen

Wenn ich das oben mit meinem Pseudo-Assembler für den Emulator kombiniere, würde aus mov 1, [1+1] Folgendes:

push 1
push 1
adds # befehl zum addieren von den obersten 2 stackelementen, ergebnis auf den stack
mov 1, [sp] # adresse des obersten stackelements auslesen und dessen wert nach speicherstelle 1

Das könnte ich dann ja übersetzen (IDs: mov=1, push=2, adds=3): 2 1 2 1 3 1 2 [sp] (Für die Darstellung von [sp] denke ich mir noch was aus, auch da gilt: Wenn ihr Ideen habt, sagt gerne was!)

Meine Frage wäre daher: Wie machen Assembler, wie zum Beispiel NASM, das? Und wie findet ihr meinen Ansatz? Habt ihr eigene Ideen? Allgemeine Kommentare und Hinweise zum Projekt?
10
Softwareentwicklung / Re: Eigene Programmiersprache
« am: 05. January 2017, 18:54 »
Es war so klar das ich irgendetwas vergesse... Hier erstmal der Link: https://github.com/Triploit-org/ny-6

Ich empfehle, wenn, ein stabiles Release (EDIT: empfehle V3.4.9, Sprungmarken müssen noch einmal ausgebessert werden) herunterzuladen, denn ich habe ein paar Funktionen eingefügt die viele Bugs erzeugen und bisher noch keine Zeit gehabt sie zu fixen.

Dann mal zu Svenska:

Die Sprache hat eine Virtuelle Laufzeitumgebung (wenn du das meinst), aber nur wenn sie interpretiert wird, beim Compiler, ist C++ ja für den Rest verantwortlich ;)

1. (Zu Punkt 1) Damit ich mit dem Parser leichteres Spiel habe ;) Ich öehne nicht direkt an Assembler an, nutze nur einen ähnlichen Syntax und ähnliche Befehle.
2. (... 2) Die Zwei Funktionsenden kommen daher, da "end" das Programm beendet und "endf" die Funktion schließt. "end" bewirkt überall im Code ein exit(0). "endf" ist wieder eine erleichterung für den Parser, um den Benutzer zu verstehen.
3. Es gibt wie du schon herausgefunden hast, einmal einen Integer und zum anderen 'nen String den man sich als Typ auswählen kann.
4. Ja, es fehlt, wird aber vom Parser soweit ignoriert. :D

Zu MasterLee:

Danke für deine tolle Ergänzung wegen der Website ;) , ich hätte es mal trotzdem hinschreiben sollen :)
Deine Sprache sieht echt hammer aus! Ich würde sowas nicht hinbekommen, der Parser würde mich umhauen :D Ob es die irgendwo zum Download gibt?



MfG :)
11
Softwareentwicklung / Eigene Programmiersprache
« am: 18. December 2016, 13:10 »
Hey !

Bevor ich mi meinem Beitrag Anfange, denke ich das es angebracht wäre wenn ich mich mal hier vorstelle:
Mich nennt man Survari, ich bin 13 und bin ein totaler Low-Level Fanatiker, auch wenn ich nicht wirklich Assembler kann (Schande über mich  :-D ). Ich programmiere seit eineinhalb Jahren C++ und bin ein leidenschaftlicher Linux-Nutzer. Außer C++ hab ich schon viele Sprachen ausprobiert. "Können", tue ich noch Java und ne andere kleine Programmiersprache auf die ich gleich zurückkommen werde  :wink: Im IRC war ich bis jetzt auch schon ein paar mal, besonders wegen tyndur
Ich hab mich hier vor ein paar Monaten angemeldet, da ich mit dem OS-Development anfangen wollte, hab da aber zu hoch gegriffen (wie ich denke alle in meinem Alter). Für eine Übung wär's zwar gut, aber da muss ich mich erst nochmal richtig mit beschäftigen.

Seit diesem Sommer arbeite ich an einer kleinen Programmiersprache, die ich erst in Java programmiert hatte, jetzt vor einem Monat aber zu C++ umgestiegen bin. Ich denke das die Sprache schon ein bisschen kann. Ihr Syntax ist dem von Assembler (keiner Spezifisch) ähnlich und ist schon so weit das man Variablen, Funktionen, Sprungmarken, Headerdateien und weiteres anlegen kann. Sie soll ein ein Projekt zum üben, aber auch ein kleiner "High-Level-Assembler" werden. Es ist auch möglich die Sprache in C++ umzuwandeln, also in die Ausgangssprache. Vielleicht ist das jetzt noch nichts allzu großes, aber ich würde mich vielleicht über etwas Feedback freuen, besonders da sich hier die Experten herumtreiben was Programmierung betrifft :-D !

Das letzte Update habe ich gerade eben hochgeladen und eine Dokumentation sowie Beispielcode ist auch verfügbar.
, sonnst hier mal ein kleines Additionsprogramm:

{main}
  defi z1,[0];
  say "Zahl eingeben: ";
  inp z1;

  add [2], z1;
  say  "Deine Zahl ergibt mit 2 addiert: ";
  prv  z1;@

  end;
  endf;

(Hab ichs doch gleich das erste mal mit dem Code-Tag verhauen  :-D )

Schönen Sontag euch noch  :-)
Seiten: [1]

Einloggen