Autor Thema: Logisim CPU  (Gelesen 78237 mal)

Jonathan

  • Beiträge: 22
    • Profil anzeigen
Gespeichert
« Antwort #260 am: 10. August 2013, 14:54 »
Ich würde dir stark empfehlen, deine CPU zunächst auf einem ATMega1284P o.ä. in Software nachzubilden, damit du ein ungefähres Gefühl dafür bekommst, was wie funktioniert, und was nicht. Direkt in Hardware anzufangen ist vielleicht doch ein wenig Overkill. Der µC kann dann ja genau die selben Signale ausgeben, wie es deine spätere richtige CPU tun wird. Höchstwahrscheinlich musst du dabei auf Bus Multiplexing zurückgreifen, aber das ist ja alles mit ein paar Latches zu schaffen.

Außerdem würde ich Interrupts wie einen ganz normalen CALL ablaufen lassen. Im Z80 ist das sogar ein "echter" CALL: Kommt ein INT rein, wird zwar im nächsten M1-Zyklus die Instruktion vom Speicher gelesen, jedoch intern durch einen CALL überschrieben und die externen Daten ignoriert. Dieser CALL pusht dann eine um seine Länge verringerte Rücksprungadresse auf den Stack und springt in die entsprechende ISR. Das ist erheblich einfacher und funktioneller als eine Variante, wo die CPU weiß, in welchem Typ Interrupt-Handler sie gerade steckt usw... Dann hat der Programmierer die volle Kontrolle darüber, wie sich die CPU verhalten soll - er kann z.B. Nested Interrupts auf Wunsch zulassen und muss sich keine Gedanken darüber machen, dass so etwas zu unkontrolliertem Verhalten führen kann. Stell dir vor, ein Interrupthandler würde vom Task Switcher (jemand könnte ihn am "Notfall-Interrupt" anschließen) unterbrochen und weggeswitcht werden - kein anderer Task könnte jetzt noch Interrupts nutzen.


Gruß
Jonathan

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #261 am: 10. August 2013, 23:42 »
Der µC kann dann ja genau die selben Signale ausgeben, wie es deine spätere richtige CPU tun wird.
Weiter oben im Thread kam die Aussage, dass die CPU nicht real aufgebaut werden soll... jetzt soll es ein FPGA werden. Und das Projekt wächst und wächst und wächst... :-)

Außerdem würde ich Interrupts wie einen ganz normalen CALL ablaufen lassen.
Das geht nicht, wenn du Supervisor-/User-Mode voneinander trennen willst. Ein Interrupt muss zusätzlich zum Call den alten Zustand wegspeichern und einen Ringwechsel durchführen; möglicherweise sogar noch den Speicherkontext wechseln. Den aktuellen Modus würde ich ungern im gewöhnlichen Flagregister haben, da er dort eine Sicherheitslücke darstellt.

Stell dir vor, ein Interrupthandler würde vom Task Switcher (jemand könnte ihn am "Notfall-Interrupt" anschließen) unterbrochen und weggeswitcht werden - kein anderer Task könnte jetzt noch Interrupts nutzen.
Naja, verschachtelte Interrupt braucht man jetzt nicht unbedingt. Selbst auf dem Z80 tut man gut daran, darauf zu verzichten (dann kann man Interrupts im zweiten Registersatz abhandeln und spart sich den Stack). Wenn man Interrupts verschachteln muss, ist das meist ein Zeichen für eine ungeeignete Programmstruktur (d.h. ISRs zu lang).

Gruß,
Svenska

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #262 am: 13. October 2013, 15:02 »
So , ich hab mich jetzt dazu entschlossen erstmal eine kleinere 16bit CPU zu bauen(hab das Datenblatt als pdf angehängt). Nun was hält ihr von meiner CPU und von meinem Datenblatt ? :D

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #263 am: 14. October 2013, 01:49 »
Hallo,

16 Befehle, 16-Bit Daten, 16-Bit Adressen, 16(+1) Register: Du magst die 16, oder? Für eine 16-Bit-CPU sind 64 KB physischer Adressraum definitiv zu wenig. Das tut gleich noch ein zweites Mal weh, weil sich Code, Daten und Stack diese 64 KB auch noch teilen müssen. Jeder Befehl hat 2 Byte, jede Konstante hat 2 Byte und da du keine komplexeren Befehle hast, brauchst du auch noch sehr viele davon.

Grundsätzlich: Du möchtest mindestens einen 24-Bit Adressraum und mindestens 32 (besser: 64, wegen Erweiterungen) Befehle. Alles darunter macht keinen Spaß beim Programmieren. (Ja, ich weiß, ein Z80 hat auch nur einen 16-Bit Adressraum, aber als über 35 Jahre alter 8-Bitter darf der das.)

An Logik bietest du AND, OR und XOR an: AND und OR braucht man ständig, XOR dagegen fast nie. Ersetze es lieber durch NOT (man braucht Negation und entweder Konjunktion oder Disjunktion, um alle anderen logischen Funktionen nachbilden zu können).

Deine Sprungbefehle sind etwas dürftig. Normalerweise möchte man JUMP (unbedingt), BRANCH (bedingt), CALL (mit Rücksprungmöglichkeit) haben. Dazu gehört dann natürlich auch ein RET, für Interrupts ein IRET. Wenn du PC als direkt adressierbares Register hat, kann man das aber alles relativ einfach nachbauen.

In deinem Datenblatt steht drin, dass dein JUMP entweder die auf das JUMP folgende Adresse anspringt (wenn Bedingung falsch) oder die auf JUMP folgende Adresse als Befehl ausführt (wenn Bedingung wahr). Ich glaube, das willst du nicht. Außerdem kann dein JUMP nur eine zur Compilezeit bekannte, konstante Adresse anspringen, keine zur Laufzeit errechnete. Wenn du PC als gewöhnliches Register betrachtest, dann braucht dein JUMP kein eigenständiger Befehl zu sein.

BRANCH-Befehle sind meist PC-relativ und können nicht über den gesamten Adressraum springen. Statt einer vollen 16-Bit-Adresse reichen vier Bedingungsbits und 12 Bit Sprungweite (8 KB, da jeder Befehl auf einer geraden Adresse liegen muss) eigentlich dicke aus. An Bedingungen würde ich mindestens A==B, A<B, A<=B, immer und deren Negationen anbieten ("nie" als negiertes "immer" ist dann ein NOP).

Die Befehle IN/OUT finde ich sehr hässlich gelöst. Wenn die Tabelle deine MMU-PageTable ist, dann kann man darauf nicht wirklich zugreifen. Wenn ich Page N (aus einem Taskregister) ändern möchte, dann steht N in einem Register und ist keine Konstante. Außerdem kannst du entweder IN/OUT für allgemeine Hardwarezugriffe vorsehen (damit hast du dann einen zweiten Bus, der z.B. nur im Kernelmodus zugreifbar ist) oder du lässt die Befehle ganz weg und blendest Hardware (inklusive MMU) in den Adressraum ein.

Flags sind an sich eine feine Sache, um relativ einfach Branches anzubieten. Um mit Zahlen größer als 16 Bit zu hantieren, musst du in der Lage sein, Übertrage (Carry) zu verarbeiten. Andere praktische Flags wären dann der CPU-Modus, ob die MMU aktiv ist oder nicht (nach Reset: MMU deaktiviert), ob Interrupts aktiv sind und das, was die Bedingungen ermöglicht.

LOAD und STORE finde ich etwas seltsam, weil die zusätzlich noch eine Addition/Subtraktion beinhalten. Vielleicht verstehe ich es gerade auch einfach nicht.

Deine Interruptlogik klingt kaputt (auch vom Deutsch her). Dein 64-Word-RAM ist zufällig die gleiche Tabelle, die auch die MMU enthält? Du schreibst PC an Adresse 17, liest dann die Adresse der ISR auf Adresse 18 und überschreibst danach Adresse 18 mit irgendwelchen Bits. Das heißt, während eine ISR aktiv ist, darf kein anderer Interrupt behandelt werden, da sonst eine zufällige Adresse angesprungen wird. Da du keine Befehle hast, die Interrupts abschalten können, kann das jederzeit passieren.

Dein Datenblatt enthält keine Informationen zum Alignment (ich vermute mal ein 16-Bit-Alignment für alles, d.h. du brauchst nur 15 Bit für eine Adresse). Wenigstens eine Rechtschreibprüfung hättest du ja spendieren können, so sieht das ziemlich hingeklatscht aus.

Was mir persönlich komplett fehlt, ist die Anbindung an den Rest der Welt. Eine CPU ist ja nicht nur zum Selbstzweck da, sondern soll ja auch irgendwas mit Ein- und Ausgaben tun. Ich vermute, dass du da nicht drüber nachgedacht hast und daher alles außer MMU in den Speicher gemappt werden muss. So sieht das Interrupthandlung auch aus.

Bevor du dich daran machst, deine CPU in VHDL/Verilog/xxx zu implementieren, solltest du einen Simulator in einer Programmiersprache deiner Wahl schreiben (je nachdem, was du gut kannst) und dann mal Anwendungen dafür schreiben. Dann siehst du, was verbesserungswürdig ist, fehlt oder einfach nur kaputt ist.

Vermutlich habe ich noch einige Punkte vergessen zu erwähnen. Auf SHL, SHR, SHAR und ROR bin ich nicht eingegangen, weil ich davon keine Ahnung habe. Wie negative Zahlen realisiert werden und ob die Unterstützung dafür ausreicht, weiß ich nicht (vermute aber mal ein nein, weil ein SIGN-Flag fehlt). Außerdem fehlt mir eine übersichtliche Bitdarstellung der Opcodes (kann man sich aber vermutlich aus dem Rest denken).

Ich gebe das Datenblatt hiermit an den Autor zurück.

Gruß,
Svenska

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #264 am: 16. October 2013, 22:25 »
Zitat
Grundsätzlich: Du möchtest mindestens einen 24-Bit Adressraum und mindestens 32 (besser: 64, wegen Erweiterungen) Befehle. Alles darunter macht keinen Spaß beim Programmieren. (Ja, ich weiß, ein Z80 hat auch nur einen 16-Bit Adressraum, aber als über 35 Jahre alter 8-Bitter darf der das.)

Nun gut dann bau ich intern meine CPU zu einer 32 bit cpu um (mit 32bit registern und einer 32bit alu) , extern hat er dann aber nur noch einen 24bit adressbus und noch den 16bit datenbus.
Die Befehlslänge Bleibt bei 16bit.

Es gibt immernoch 16 Befehle und 16 register.
Das Registerset ist folgenermaßen aufgebaut:
Reg0 = Nullregister,
Reg1-Reg12 = Normale Register
R13 = Flagregister
R14 = Linkregister
R15 = Programmcounter


So das hier sind die 16 Befehle:
  • add
  • sub
  • and
  • or
  • not
  • shift
  • jump
  • store
  • load
  • softint/Ret
  • mul
  • addi
  • subi
  • loadByte
  • addc
  • sbb

die Befehle addi/subi sind nur 2 operanten Befehle(also z.b addi Reg1, 0xFF, also Reg1 = Reg1 + 0xFF )
In den Befehlen Store und Load gibts noch zusätzlich einen 4bit offset.Bei Store wird der 4bit offset von dem adressregister abgezogen und bei load draufaddiert(so kann man Load/store als normale Load/store benutzen indem man den offset auf 0 lässt, oder man verwendet die 2 Befehle als Pop/push indem man den offset auf z.b 1 setzt.)Auserdem werden Bei Load/store nur die unteren 16 bits des Quell bzw Zielregister benutzt.

der befehl shift so aufgebaut: 0-3 Opcode | 4-6 Shiftart | 8-12 shiftweite | 13-16 Register.
shiftart : srl, sll, ror, sra
die shiftweite sagt aus wie weit geshift werden soll

Beim Befehl jump werden keine Flags gesetzt sondern nur geschaut welche Flags gesetzt sind, auserdem kann man über ein Bit entscheiden -falls die richtigen flags gesetzt sind- ob ein Wert aus einem Register oder einen Kostanten wert ( der an der adresse liegt auf dem der PC zeigt) in den PC geladen wird.

Der Befehl LoadByte ladet einen 8 bitwert in das gesamt Register.
« Letzte Änderung: 17. October 2013, 13:38 von Tufelix »

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #265 am: 02. November 2013, 23:11 »
Nun hab ein Weiteres Datenblatt geschrieben, für eine 32bit risc- CPU mit 16 bit Befehlen.

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #266 am: 03. November 2013, 13:15 »
So hab jetzt mal das datenblatt und die CPU nochmal ein wenig überarbeitet.

Edit: anscheindend ist das Datenblatt etwas zu groß, hab jetzt mal 1 satz rausgestrichen:

LoadRs und Store Rs sind nur im Systemodus aktiv, im Usermodus stellen sie ein nop dar.
« Letzte Änderung: 03. November 2013, 14:27 von Tufelix »

 

Einloggen