Der Prozessor der CPU Sandbox in drei Stufen: MiniCPU 8, 8S und 8X. Hier stehen seine Register, Flags und alle Befehle mit Opcode, Länge und Takten, dazu der Stack, die Register X und Y, der Maschinencode und der Assembler. Die Tabellen stammen aus denselben Tabellen, mit denen die Sandbox selbst rechnet.
Jede Stufe behält alles von der vorigen und fügt eine Idee hinzu. Welche Stufe ein Programm braucht, steht in seiner ersten Zeile: .cpu 8, .cpu 8s oder .cpu 8x (in C #pragma cpu 8x). Ohne diese Zeile läuft ein Assembler-Programm auf der MiniCPU 8, ein C-Programm auf der 8X. Das Menü »CPU« in der Sandbox ändert die Zeile.
| MiniCPU 8 | MiniCPU 8S | MiniCPU 8X | |
|---|---|---|---|
| Idee | Register, Flags, Sprünge | + ein Stack | + viel Speicher und Zeiger |
| RAM | 256 Bytes | 256 Bytes | 65.536 Bytes (64 KiB) |
| Adressen | 1 Byte | 1 Byte | 2 Bytes |
| Register |
A B C D, PC
|
+ SP
|
+ X Y (16 Bit) mit XL XH YL YH
|
| Flags |
ZF CF
|
ZF CF
|
ZF CF
|
| Neue Befehle | – |
PUSH POP CALL RET, [SP+n], ADD/SUB SP, n
|
keine: die bekannten Befehle können X und Y |
| Geräte | auf allen Stufen dieselben, über IN und OUT (Geräte) |
||
Auf allen drei Stufen gelten dieselben Regeln. Wer eine Stufe weitergeht, muss nichts umlernen.
LDI A, 5 lädt 5 in A, ADD A, B ändert A, STA [0x70], A schreibt nach 0x70.LDI A, 0x70 lädt die Zahl 0x70, LDA A, [0x70] das Byte, das an Adresse 0x70 steht, und LDA A, [B] das Byte an der Adresse, die in B steht.ADD A, B und ADD A, 16, AND A, 0x0F, CMP A, 10.LDI, LDA, STA, MOV, ADD, SUB, CMP, INC, DEC, PUSH und POP auch mit X und Y.Ob eine Zahl dezimal (112), hexadezimal (0x70) oder binär (0b0111_0000) dasteht, ist gleich; mehr dazu unter Assembler.
0x00 bis 0xFF. Programm, Daten und Bildschirm teilen sich diesen Speicher; nichts hindert ein Programm daran, seine eigenen Befehle zu überschreiben.0x00 und läuft, bis sie auf HLT trifft.ADC und SBC rechnen den Übertrag des Befehls davor mit, so addiert ADD B, D und danach ADC C, 0 den Wert D zur 16-Bit-Zahl in C (oben) und B (unten).Beim Start stehen A bis D und die Flags auf 0.
Zwei Flags halten fest, wie der letzte Rechenbefehl ausgegangen ist. Die Sprungbefehle entscheiden danach.
| Flag | ist 1, wenn … |
|---|---|
ZF (Zero) |
das Ergebnis 0 ist. Nach CMP: beide Werte gleich waren. |
CF (Carry) |
es einen Übertrag aus Bit 7 gab (ADD, ADC, INC, SHL), etwas geborgt werden musste, weil der erste Wert kleiner war (SUB, SBC, DEC, CMP), oder SHR eine 1 hinausgeschoben hat. |
Ein Beispiel: 3 − 5 passt nicht in ein Byte.
LDI A, 3 LDI B, 5 SUB A, B ; 3 − 5 HLT

Nach SUB A, B steht in A 254, also 3 − 5 + 256, und CF = 1 sagt: Es musste geborgt werden. Die Sandbox hebt hervor, was der letzte Befehl geändert hat.
AND, OR und XOR setzen ZF nach ihrem Ergebnis und löschen CF immer; CLF löscht beide. Laden, Speichern, MOV, Sprünge, der Stack und die Geräte lassen die Flags, wie sie sind. In der Befehlstabelle steht , wo ein Befehl das Flag nach seinem Ergebnis setzt, 0, wo er es löscht, und –, wo er es nicht verändert.
Vergleichen. CMP A, B rechnet A − B, behält aber nur die Flags. Danach gilt (für Zahlen von 0 bis 255):
| Bedingung | Flags | Sprung |
|---|---|---|
| A = B | ZF = 1 | JZ |
| A ≠ B | ZF = 0 | JNZ |
| A < B | CF = 1 | JC |
| A ≥ B | CF = 0 | JNC |
| A > B | CF = 0 und ZF = 0 | andersherum vergleichen: CMP B, A, dann JC
|
In der Sandbox erklärt die Maus über ZF und CF, warum der letzte Befehl das Flag gesetzt oder gelöscht hat.
Alle Befehle der MiniCPU 8. Die Beispiele stehen für alle Register: Statt ADD A, B geht auch ADD C, D, statt 5 jede Zahl von 0 bis 255. Bei bedingten Sprüngen stehen zwei Taktzahlen: ohne Sprung / mit Sprung.
| Befehl | Wirkung | ZF | CF | Bytes | Takte | Opcode |
|---|---|---|---|---|---|---|
| Steuerung | ||||||
NOP |
Tut nichts. | – | – | 1 | 1 | 0x00 |
HLT |
Hält die CPU an. | – | – | 1 | 1 | 0x01 |
CLF |
Löscht ZF und CF. | 0 | 0 | 1 | 1 | 0x02 |
| Daten bewegen | ||||||
LDI A, 42 |
Lädt die Zahl 42 in A. | – | – | 3 | 3 | 0x10 |
LDA A, [0x70] |
Lädt das Byte an Adresse 0x70 in A. | – | – | 3 | 4 | 0x11 |
LDA A, [B] |
Lädt das Byte an der Adresse, die in B steht. | – | – | 2 | 3 | 0x12 |
STA [0x70], A |
Speichert A an Adresse 0x70. | – | – | 3 | 4 | 0x13 |
STA [B], A |
Speichert A an der Adresse, die in B steht. | – | – | 2 | 3 | 0x14 |
MOV A, B |
Kopiert B nach A. | – | – | 2 | 2 | 0x15 |
| Rechnen | ||||||
ADD A, B |
A = A + B. CF = Übertrag aus dem obersten Bit. | 2 | 2 | 0x20 |
||
ADD A, 5 |
A = A + 5. | 3 | 3 | 0x21 |
||
SUB A, B |
A = A − B. CF = geborgt (A war kleiner). | 2 | 2 | 0x22 |
||
SUB A, 5 |
A = A − 5. | 3 | 3 | 0x23 |
||
ADC A, B |
A = A + B + CF: addiert den Übertrag eines ADD davor. | 2 | 2 | 0x24 |
||
ADC A, 0 |
A = A + 0 + CF. | 3 | 3 | 0x25 |
||
SBC A, B |
A = A − B − CF: zieht das Geborgte eines SUB davor ab. | 2 | 2 | 0x26 |
||
SBC A, 0 |
A = A − 0 − CF. | 3 | 3 | 0x27 |
||
INC A |
A = A + 1. | 2 | 2 | 0x28 |
||
DEC A |
A = A − 1. | 2 | 2 | 0x29 |
||
SHL A |
Schiebt nach links; das oberste Bit kommt nach CF. | 2 | 2 | 0x2A |
||
SHR A |
Schiebt nach rechts; Bit 0 kommt nach CF. | 2 | 2 | 0x2B |
||
| Logik | ||||||
AND A, B |
Bitweises UND. | 0 | 2 | 2 | 0x30 |
|
AND A, 0x0F |
Bitweises UND mit einer Zahl. | 0 | 3 | 3 | 0x31 |
|
OR A, B |
Bitweises ODER. | 0 | 2 | 2 | 0x32 |
|
OR A, 0x80 |
Bitweises ODER mit einer Zahl. | 0 | 3 | 3 | 0x33 |
|
XOR A, B |
Bitweises exklusives ODER. | 0 | 2 | 2 | 0x34 |
|
XOR A, 0xFF |
Bitweises exklusives ODER mit einer Zahl. | 0 | 3 | 3 | 0x35 |
|
| Vergleichen | ||||||
CMP A, B |
Rechnet A − B, nur für die Flags. | 2 | 2 | 0x40 |
||
CMP A, 10 |
Rechnet A − 10, nur für die Flags. | 3 | 3 | 0x41 |
||
| Springen | ||||||
JMP label |
Springt immer. | – | – | 2 | 3 | 0x60 |
JZ label |
Springt, wenn ZF = 1 (null, nach CMP: gleich). | – | – | 2 | 2 / 3 | 0x61 |
JNZ label |
Springt, wenn ZF = 0. | – | – | 2 | 2 / 3 | 0x62 |
JC label |
Springt, wenn CF = 1 (Übertrag, nach CMP: kleiner). | – | – | 2 | 2 / 3 | 0x63 |
JNC label |
Springt, wenn CF = 0. | – | – | 2 | 2 / 3 | 0x64 |
| Ein- und Ausgabe | ||||||
IN A, 0x02 |
Liest ein Byte vom Gerät an Port 2. | – | – | 3 | 4 | 0x70 |
OUT 0x11, A |
Schickt A an das Gerät an Port 0x11. | – | – | 3 | 4 | 0x71 |
0x100, eins hinter dem Ende des RAMs. PUSH A zieht 1 von SP ab und speichert A dort; POP A liest das Byte und zählt SP wieder hoch. Der Stack wächst also durch das gewöhnliche RAM nach unten.CALL label legt die Adresse des nächsten Befehls auf den Stack und springt; RET holt sie wieder herunter und macht dort weiter. Aufrufe dürfen verschachtelt sein.LDA A, [SP+n] und STA [SP+n], A lesen und schreiben n Bytes über SP, ohne etwas vom Stack zu nehmen. SUB SP, n macht Platz für n Bytes, ADD SP, n gibt sie wieder frei.Diese Befehle kommen auf der 8S zu allen Befehlen der MiniCPU 8 hinzu.
| Befehl | Wirkung | ZF | CF | Bytes | Takte | Opcode |
|---|---|---|---|---|---|---|
| Stack | ||||||
PUSH A |
SP = SP − 1, dann A nach [SP]. | – | – | 2 | 3 | 0x50 |
POP A |
A aus [SP], dann SP = SP + 1. | – | – | 2 | 3 | 0x51 |
CALL label |
Legt die Rücksprungadresse auf den Stack und springt. | – | – | 2 | 4 | 0x52 |
RET |
Nimmt die Rücksprungadresse vom Stack und springt dorthin. | – | – | 1 | 3 | 0x53 |
LDA A, [SP+2] |
Lädt das Byte 2 Plätze über SP: ein Argument oder eine lokale Variable. | – | – | 3 | 4 | 0x54 |
STA [SP+2], A |
Speichert A 2 Plätze über SP. | – | – | 3 | 4 | 0x55 |
ADD SP, 3 |
Gibt 3 Bytes auf dem Stack frei (lokale Variablen, oder Argumente nach dem Aufruf). | – | – | 2 | 3 | 0x56 |
SUB SP, 3 |
Macht auf dem Stack Platz für 3 Bytes lokale Variablen. | – | – | 2 | 3 | 0x57 |
So rufen sich Funktionen gegenseitig auf; der C-Compiler macht es bei jeder Funktion genauso:
PUSH auf den Stack und ruft die Funktion mit CALL auf.SUB SP, n Platz für ihre lokalen Variablen.[SP+n]: zuerst kommen die lokalen Variablen, dann die Rücksprungadresse, dann die Argumente.ADD SP, n frei und kehrt mit RET zurück, das Ergebnis in A.ADD SP, n frei..cpu 8s LDI A, 21 PUSH A ; das Argument CALL double ADD SP, 1 ; Argument wieder freigeben OUT 0x11, A ; zeigt 42 HLT double: LDA A, [SP+1] ; [SP+0] ist die Rücksprungadresse ADD A, A RET

Nach CALL double liegt ganz oben im RAM, an 0xFF, das Argument 21 (15) und darunter, an 0xFE, die Rücksprungadresse 07: Dort steht ADD SP, 1. Die RAM-Ansicht markiert den Stack.
Jeder Aufruf hat seine eigenen Argumente und lokalen Variablen, deshalb darf eine Funktion sich selbst aufrufen. Einen eigenen Rahmenzeiger gibt es nicht, alle Abstände zählen ab SP. Jedes PUSH mitten in einer Funktion verschiebt sie deshalb um eins. Am einfachsten reserviert eine Funktion alle lokalen Variablen gleich am Anfang. Auf der 8X ist die Rücksprungadresse zwei Bytes lang, die Argumente beginnen dort ein Byte höher.
0x0000 bis 0xFFFF: alles, was eine 16-Bit-Adresse erreicht, so wie 256 Bytes alles sind, was 8 Bit erreichen. Platz für Tabellen, große Bildschirme mit zwei Seiten und längere Programme.LDA A, [0x0812], STA [0x0812], A, Sprünge und CALL werden dadurch ein Byte länger und einen Takt langsamer.0x10000, ganz oben. X und Y stehen beim Start auf 0. CALL legt eine Rücksprungadresse mit zwei Bytes ab.LDA A, [B] gibt es weiter, aber B erreicht nur die ersten 256 Bytes. Für alles darüber sind X und Y da.X und Y sind zwei Register mit 16 Bit, gedacht als Zeiger. Sie brauchen keine neuen Befehle:
| Befehl | Wirkung |
|---|---|
LDI X, 0x0800 |
lädt eine 16-Bit-Zahl |
LDA X, [0x0700] und STA [0x0700], X
|
lädt und speichert zwei Bytes, das untere zuerst |
LDA X, [SP+2] und STA [SP+2], X
|
dasselbe auf dem Stack |
MOV X, Y |
kopiert |
ADD X, 32, ADD X, A oder ADD X, Y
|
addiert eine Zahl, ein Byte (0–255) oder ein 16-Bit-Register; SUB ebenso |
CMP X, 0x0900 oder CMP X, Y
|
vergleicht alle 16 Bit |
INC X und DEC X
|
zum nächsten oder vorigen Byte |
PUSH X und POP X
|
zwei Bytes auf den Stack und zurück |
LDA A, [X] und STA [X], A
|
X als Zeiger: das Byte an der Adresse, die in X steht |
Bei X und Y beziehen sich ZF und CF auf alle 16 Bit. Schieben, Logik, ADC und SBC gibt es nur für A bis D, und ein 8-Bit-Register kann kein 16-Bit-Register aufnehmen: ADD A, X geht nicht.
XL, XH, YL, YH sind das untere und das obere Byte von X und Y. Sie funktionieren nur mit MOV, in beide Richtungen zwischen ihnen und A bis D: MOV XH, A, MOV B, YL.
Breiter ist langsamer: Eine Adresse hat zwei Bytes, eine Zahl für X oder Y auch, Laden und Speichern bewegen bei X und Y zwei Bytes, und die 8-Bit-Recheneinheit braucht für 16-Bit-Rechnungen einen Takt mehr. Die Tabelle nennt Bytes und Takte für jeden Befehl, der auf der 8X anders kostet oder X und Y kann; alle anderen kosten so viel wie auf der 8.
| Befehl | 8 / 8S | 8X | 8X mit X oder Y |
|---|---|---|---|
LDI A, 42 |
3 Bytes, 3 Takte | 3 Bytes, 3 Takte | 4 Bytes, 5 Takte |
LDA A, [0x70] |
3 Bytes, 4 Takte | 4 Bytes, 5 Takte | 4 Bytes, 6 Takte |
STA [0x70], A |
3 Bytes, 4 Takte | 4 Bytes, 5 Takte | 4 Bytes, 6 Takte |
MOV A, B |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
ADD A, B |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
ADD A, 5 |
3 Bytes, 3 Takte | 3 Bytes, 3 Takte | 4 Bytes, 5 Takte |
SUB A, B |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
SUB A, 5 |
3 Bytes, 3 Takte | 3 Bytes, 3 Takte | 4 Bytes, 5 Takte |
INC A |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
DEC A |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
CMP A, B |
2 Bytes, 2 Takte | 2 Bytes, 2 Takte | 2 Bytes, 3 Takte |
CMP A, 10 |
3 Bytes, 3 Takte | 3 Bytes, 3 Takte | 4 Bytes, 5 Takte |
PUSH A |
2 Bytes, 3 Takte | 2 Bytes, 3 Takte | 2 Bytes, 4 Takte |
POP A |
2 Bytes, 3 Takte | 2 Bytes, 3 Takte | 2 Bytes, 4 Takte |
CALL label |
2 Bytes, 4 Takte | 3 Bytes, 6 Takte | – |
RET |
1 Bytes, 3 Takte | 1 Bytes, 4 Takte | – |
LDA A, [SP+2] |
3 Bytes, 4 Takte | 3 Bytes, 4 Takte | 3 Bytes, 5 Takte |
STA [SP+2], A |
3 Bytes, 4 Takte | 3 Bytes, 4 Takte | 3 Bytes, 5 Takte |
JMP label |
2 Bytes, 3 Takte | 3 Bytes, 4 Takte | – |
JZ label |
2 Bytes, 2 / 3 Takte | 3 Bytes, 3 / 4 Takte | – |
JNZ label |
2 Bytes, 2 / 3 Takte | 3 Bytes, 3 / 4 Takte | – |
JC label |
2 Bytes, 2 / 3 Takte | 3 Bytes, 3 / 4 Takte | – |
JNC label |
2 Bytes, 2 / 3 Takte | 3 Bytes, 3 / 4 Takte | – |
Der Regler »Clock« stellt den Takt von 1 Hz bis 64 MHz ein, und die CPU läuft in diesem Takt in Echtzeit. Wie viele Takte ein Befehl braucht, folgt aus einem einfachen Modell einer 8-Bit-CPU, deren Tempo der Speicherbus begrenzt:
JMP, ein bedingter Sprung, der springt, CALL, RET),
So braucht ADD A, B 2 Takte, LDI A, 5 3, LDA A, [0x70] 4 (auf der 8X 5) und JNZ loop 2, wenn es nicht springt, und 3, wenn es springt. Die Statuszeile der Sandbox zeigt die verbrauchten Takte und wie lange sie beim gewählten Takt dauern; »Cycles« zeigt einen Befehl Takt für Takt.
Neben dem Code zeigt die Sandbox die Adresse und die Bytes jeder Zeile:
loop: LDI A, 5 ADD A, B MOV B, A JMP loop

So entstehen sie:
0x Steuerung, 1x Daten bewegen, 2x Rechnen, 3x Logik, 4x Vergleichen, 5x Stack, 6x Springen, 7x Ein- und Ausgabe. Höhere Stufen fügen Opcodes hinzu, nummerieren aber keine um.ADD A, B ist 20 01, MOV B, A ist 15 10.LDI A, 5 ist 10 00 05, LDI X, 0x0800 ist 10 08 00 08.JMP 0x23 ist auf der 8 60 23, auf der 8X 60 23 00.Ein Byte, das kein Befehl ist, führt zu einer Fehlermeldung, ebenso ein Befehl, den die Stufe noch nicht kennt.
Eine Zeile enthält höchstens einen Befehl. Groß- und Kleinschreibung der Befehle ist egal, Kommentare beginnen mit ;.
| Schreibweise | Bedeutung |
|---|---|
loop: |
Eine Marke ist die Adresse des Bytes, das danach kommt. Sie beginnt mit einem Buchstaben oder _; ein Befehl darf in derselben Zeile folgen. |
.cpu 8x |
wählt die Stufe: 8, 8s oder 8x. Ohne diese Zeile die MiniCPU 8. |
.org 0x60 |
legt die folgenden Bytes ab dieser Adresse ab. |
.db 1, 2, "Hi", 0 |
schreibt Bytes ins RAM: Zahlen, Zeichen oder Texte. |
WIDTH .equ 16 |
eine Konstante mit Namen. |
.display 0x80, 16, 8, gray8 |
schlägt vor, wie der Bildschirm das RAM zeigt (Bildschirm). |
Werte schreibst du dezimal 42, hexadezimal 0x2A, binär 0b0010_1010 oder als Zeichen 'A' (65). Marken und Konstanten lassen sich mit + - * / & | << >> und Klammern verrechnen, etwa screen + 16 * 3 oder table >> 8. Negative Bytes laufen über: -1 ist 255.
Die Sandbox übersetzt schon beim Tippen. Fehler erscheinen unter dem Editor, ein Klick springt in die Zeile. Die Meldungen erklären mit diesen Regeln, was gemeint war: Auf LDA A, 0x70 antwortet sie, dass LDA den Speicher liest, und schlägt LDA A, [0x70] oder LDI A, 0x70 vor.
Die MiniCPU gibt es nur in der CPU Sandbox; diese Seite ist ihre Referenz. Im Repository der Sandbox steht dazu: