4-Bit Prozessor, Teil 1 – Projektbeschreibung

Wie viele aus meiner Generation habe ich oft darüber nachgedacht einen eigenen Prozessor zu implementieren. Das hat keinen praktischen Nutzen, es geht lediglich darum eine Vielzahl von Techniken zu erlernen. Man muss sich einen sinnvollen Befehlssatz überlegen, die notwendigen Entwicklungswerkzeuge implementieren, eine Prozessorarchitektur entwerfen und letztendlich das Teil auch tatsächlich realisieren. Die ersten Schritte sind für mich als Softwareentwickler relativ einfach. Der letzte Teil ist für mich die größte Herausforderung. Ich kann im Augenblick noch nicht absehen, wie weit ich tatsächlich komme und das Projekt wird sicherlich eine Weile dauern.

Ich habe mich für einen 4-Bit Prozessor entschieden damit die Hardware möglichst überschaubar bleibt. Da er am Ende ohnehin keinen sinnvollen Nutzen hat, ist es auch egal wie leistungsfähig er ist. Hauptsache ist, dass er läuft.

Der Befehlssatz ist an eine RISC Architektur angelehnt. Das heißt, es gibt logische und arithmetische Funktionen nur auf den Registern, keine direkte Speichermanipulation. Es ist aber kein echter RISC Befehlssatz, da ich variable lange Befehlssequenzen habe. Um die Codewörter möglichst kurz zu halten, habe ich zwei Beschränkungen gewählt. Zum einen gibt es nur vier Register (zwei Bit für die Registercodierung) und zum anderen wird mit einem Akkumulator gearbeitet, der bei Zwei-Operanden Befehlen implizit verwendet wird. Die meisten (alle?) Ein-Operanden Befehle können aber auch auf den Registern ausgeführt werden.

Im Überblick eine bullet point Liste mit den geplanten Eigenschaften:

  • 4 Bit Operationen (im Wesentlichen)
  • Von Neumann Architektur
  • 4 Register plus Akkumulator
  • 2 Indexregister
  • 1 Stackpointer
  • 12 Bit Adressen für PC, SP, Indexregister
  • 1 8 Bit Zero Page Register, analog zum Direct Page Register im 6809, 4 Bit Offset bei Verwendung
  • Befehlslänge 2 bis 5 Digits, jeweils 4 Bit

Die Anzahl der Adressierungsarten ist aus Gründen der einfachen Hardware beschränkt.

  • Immediate – nur für Ladevorgänge
  • Absolute – Laden, Speichern und Sprünge (Jump und Call)
  • PC Relative – nur für Branch, mit Condition Code, 8 Bit signed offset
  • ZP Relative – Laden und Speichern, 4 Bit lower digit (ZP liefert die oberen 8 Bit)
  • Indexregister – nur Laden und Speichern
  • Register – Register Transfer

In einem ersten Schritt wird ein Assembler für den geplanten Befehlssatz und ein Simulator zur Ausführung entwickelt. Die Ausführung im Simulator hat den Vorteil, dass man viel schneller prüfen kann, ob der Befehlssatz sinnvoll eingesetzt werden kann. Zudem sind Testdurchläufe viel schneller auszuführen und ein debugging des Verhaltens wesentlich einfacher.

Der Befehlssatz sieht vorläufig so aus:


00rr 0000 o1        ld R1..4 zero page 4 bit offset
00ix 0001 o1        ld IX1 IX2 A ZP 4 bit offset
00rr 0010           ld A from R1..4
0000 0011 v1 v2     ld ZP imme 8 bit value
0001 0011 v1        ld A imme 4 bit
0010 0011           ld ZP low digit from accu
0011 0011           ld ZP high digit from accu
00rr 0100 v1 v2 v3  ld R1..4 from abs address
00ix 0101 v1 v2 v3  ld IX1 IX2 A SP from abs address
00rr 0110 v1        ld R1..4 imme 4 bit value
00ix 0111 v1 v2 v3  ld IX1 IX2 SP ZP 12 / 8 bit imme value
00rr 1000           ld R1..4 from [IX1]
00rr 1001           ld R1..4 from [Ix2]
00rr 1010
00rr 1011

01rr 0000 o1        st R1..4 zero page 4 bit offset
01ix 0001 o1        st SP IX1 IX2 zero page 4 bit offset
01rr 0010           st A to R1..4
01?? 0011
01rr 0100 v1 v2 v3  st R1..4 to abs address
01ix 0101 v1 v2 v3  st IX1 IX2 A SP to abs address
01?? 0010
01?? 0011
01rr 1000           st R1..4 to [IX1]
01rr 1001           st R1..4 to [Ix2]
01rr 1010
01rr 1011

1000 d1 d2 d3       jmp 12 bit address
1001 d1 d2 d3       call 12 bit address
1010 cc d1 d2       bra 8 bit signed offset
1011 ????

11rr 0000           add
11rr 0001           sub
11rr 0010           addc
11rr 0011           subb
11rr 0100           and
11rr 0101           or
11rr 0110           xor
11rr 0111           inc R1..4
11rr 1000           dec R1..4
11ix 1001           inc IX1 IX2 A ZP
11ix 1010           dec IX1 IX2 A ZP
1100 1011           not A
1101 1011           neg A
1110 1011           stc set carry
1111 1011           clc clear carry
11rr 1100           cmp
11?? 1101
1100 1110 m1 m2     push m1 mask R1..4, m2 mask IX1 IX2 A ZP
1101 1110 m1 m2     pull
1100 1111           clr A
1101 1111           
1110 1111           ret
1111 1111           nop

Im Augenblick sind ca. 200 von den 256 möglichen Codewörtern belegt. Aber es werden vermutlich noch ein paar Befehle hinzu kommen. Da geplant ist, die Ausführung über einen Microcode Sequenzer zu steuern, ist es aus technischen Gründen nicht besonders wichtig, dass der Code möglichst symmetrisch ist – das ist eher eine Frage der Ästhetik. Die Befehle werden alle durch die beiden ersten Digits bestimmt, die nachfolgenden 0 bis 3 Digits sind dann nur noch Parameter, wie z.B. Adressen oder Offset Werte. Das Microcode ROM wird einfach 256 Einsprungpunkte erhalten. Die beiden letzten Aktionen einer Sequenz bestehen darin, die beiden Codeteile für den nächsten Befehl in das Code Register zu laden.

Nach einem Reset wird das Code Register mit dem jmp Befehl (1000 1101) und der Program Counter mit ffe initialisiert. Der Microcode dazu lädt dadurch den Startpunkt für das Programm aus der Adresse ffd aus (die letzten drei Digits) und springt an diese Adresse.

Vermutlich wird es im Laufe des Projekts hier noch die eine oder andere Änderung geben. Es ist halt ein Lern- und Forschungsprojekt welches nicht vorab vollständig geplant werden kann.