Autor Thema: Logisim CPU  (Gelesen 72412 mal)

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #80 am: 05. January 2013, 13:06 »
Nun gut, werde dann wohl jetzt ein Risc-CPU mit Harvard-architektur bauen. Meine bisherigen CPU's haben die Befehle immer mit einem Takt abgearbeitet, daher wird es kein problem sein dies auch in meinem neuem CPU umzusetzen. Auserdem hab ich mir gedacht das man doch so nen Dualkern-CPU bauen könnte, jeder der 2 Kerne bekommt dann seinen eigenen Ram und Programmspeicher und über das Round-Robin-Verfahren könnten dann jeder der 2 Kerne auf das externe Bussystem zugreifen. Was haltet ihr von der idee ?  :-D

chris12

  • Beiträge: 134
    • Profil anzeigen
Gespeichert
« Antwort #81 am: 05. January 2013, 15:53 »
warum baust du nicht erstmal was funktionierendes simples?
OS? Pah! Zuerst die CPU, dann die Plattform und _dann_ das OS!

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #82 am: 05. January 2013, 16:26 »
Getrennte CPUs mit getrennten Speichern sind getrennte Systeme, also kein Dual-Core. Und zwei Geräte (CPUs) an einem Bussystem ist ein normales Multi-Master-Bussystem, wie oben beschrieben. Außerdem mag es Hardware garnicht, gleichzeitig von verschiedenen Treibern angesprochen zu werden.

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #83 am: 05. January 2013, 18:02 »
Naja war nur so ne Idee...nun ich hab jetzt mal eine CPU gezeichnet:

(Die buffer kann man eigentlich weg lassen, weil der Bus mit der gleichen Schaltfrequenz arbeitet wie die CPU)

So Jetzt zum Bus:

Der Control bus besteht aus 2 Leitungen : R/W und Takt.

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #84 am: 05. January 2013, 18:21 »
Endlich mal'n klar struktierirtes Schaubild das ich verstehe :-D
Also die Allzweckregister liegen in nem Datenbuffer.
Stackpointer entspricht esp.
framepointer -> ebp
Addresspointer -> ?
P.counter -> eip zeigt aber nicht auf eine SpeicherAdresse in der RAM sondern in der ROM, weil die Firmware dort fest eingedrahtet ist.
Der Instruction Decoder sucht sich jetzt den Befehl auf der addresse vom P.Counter decoded et und leitet den Strom weiter zu den ALU die Instruktion ausführen.

« Letzte Änderung: 05. January 2013, 18:43 von Martin Erhardt »

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #85 am: 05. January 2013, 18:41 »
Hallo,


Vermutlich. Und der einzige, der noch keine Ergebnisse hat. *duck* :-D
Hm, vielleicht bin ich aber auch der einzige der ein Projekt hat bei dem man auf keinen Fall am Ende feststellen möchte das man am Anfang etwas falsch gemacht hat (egal wie klein dieses etwas auch sein mag).
Deswegen beschäftige ich mich zur Zeit mit der Funktionsweise von Floating-Point-Befehlen obwohl ich erstmal gar keine implementieren möchte, nur um Heute schon Flag-Register/Exception-Handling/.... anständig designen zu können (Floating-Point soll bei mir integraler (aber optionaler) Bestandteil der CPU sein und nicht als zusätzliches Rechenwerk oder gar zusätzlicher Coprozessor realisiert werden) und hoffentlich später nicht feststellen muss das ich etwas übersehen habe und dann entscheiden darf ob ich eine Krücke dranbastle oder noch mal von vorne anfange.

Womit hätte ich denn sonst C lernen sollen, wenn nicht mit einem Projekt, das mich interessiert?
Soll das heißen das es im User-Mode nichts gibt was Dich mal interessieren würde? ;)


jeder der 2 Kerne bekommt dann seinen eigenen Ram und Programmspeicher und über das Round-Robin-Verfahren könnten dann jeder der 2 Kerne auf das externe Bussystem zugreifen.
Wenn Du da "Ram" und "Programmspeicher" gegen L1-Data/Code-Cache austauscht und dann noch anständige Cache-Kohärenz schaffst hast Du echtes SMP (was Du beschreibst ist AMP).
Ich will jetzt wirklich nicht überheblich klingen aber ich schätze mal dass das Heute die Grenzen Deiner Möglichkeiten ein ganz klein wenig überschreitet.
Du solltest wirklich erst mal mit was ganz einfachem Anfangen: erst mal eine kleine CPU nach Harvard und als einzigste Peripherie ein paar Taster und LEDs (noch nicht mal Data-RAM, für die ersten kleinen Programmchen reichen die Register). Erst nachdem Du das erfolgreich geschafft hast würde ich mal einen UART als Schnittstelle zur Außenwelt und RAM dazu packen und erst dann wenn Deine Programme deutlich komplexer werden und wirklich mehr Möglichkeiten brauchen solltest Du weiter denken. Vor allem sammelst Du so Erfahrungen noch bevor die Größe Deines Projekts in Mann-Jahre gemessen werden muss und ein Fehlschuss in zu vielen Tränen endet.

Dein CPU-Diagramm sieht nach einem realistischem Plan aus, beim BUS fehlen noch 2 Byte-Enable-Leitungen falls Du nicht nur 16Bit-Weise zugreifen willst und die Adressleitung A0 kann eventuell eingespart werden (wird durch die 2 Byte-Enables erledigt) falls Du Byteweise adressieren möchtest (falls Dein BUS als kleines Datum 16Bit-Werte verarbeitet/adressiert ist er so aber Okay).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #86 am: 05. January 2013, 18:43 »
Ich weiß aber garnicht ob RISC CPUs überhaupt explizite Stackpointerregister haben. Bei 28 Registern ist das im Normalfall wohl gar nicht notwendig. Und wenn doch kann man dafür wohl einfach ein Allzweckregister benutzen.
Instruktionen wie push und pop,welche explizite Stackpointerregister vorraussetzen, sind ja Memory/register mixed Instruktionen,die komplex sind und in einem RISC garnicht vorkommen dürfen. <- keine Garantie,dass meine These stimmt.
« Letzte Änderung: 05. January 2013, 19:21 von Martin Erhardt »

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #87 am: 05. January 2013, 19:21 »
Hallo,


Ich weiß aber garnicht ob RISC CPUs überhaupt ein explizite Stackpointerregister haben....
Eigentlich nicht, echte RISC-CPUs geben nicht mal vor ob der Stack nach unten oder nach oben wächst (das ist dann eine Vorgabe des OS oder Compilers). Bei ARM sind im Assembler z.B. PUSH und POP nur Aliase für Speicherzugriffe mit R13 (ist bei ARM der "offizielle" Stackpointer) und Pre-Decrement bzw. Post-Inkrement aber das könnte man auch ganz anders lösen da diese Befehle mit jedem Register als Adressregister umgehen können.
Das Problem ist eher der erforderliche Schaltungsaufwand in der CPU (jedes beliebige Register als Adressregister benutzen zu können erfordert mehr Logik als immer nur bestimmte Register zu benutzen) und der verfügbare Platz in den OpCodes (PUSH und POP für die normalen Register sind bei 8086 nur 1 Byte groß aber bei ARM immer 32Bit, früher war das durchaus relevant aber bei heutigen Speicherpreisen ist das nicht mehr so schlimm). Ein anderes Problem sind die verfügbaren Adressierungsmodi, bei x86 gibt es kein Pre/Post-Inkrement/Dekrement so das sich PUSH und POP gar nicht ohne zusätzliche Rechenbefehle (die dann auch noch die Flags modifizieren) mit normalen Speicherzugriffen ersetzen ließen.
Ein anderes Problem ist die Performance. So ist z.B.
pop  ecx
pop  ebx
pop  eax
langsamer als
mov  ecx,[esp]
mov  ebx,[esp+4]
mov  eax,[esp+8]
add  esp,12
da im ersten Code jeder der POP-Befehle warten muss bis der vorangegangene POP-Befehl ESP fertig modifiziert hat bevor er weiß von wo er Daten lesen soll wohingegen im zweiten Code alle 4 Befehle parallel ausgeführt werden können (zumindest theoretisch) solange sichergestellt ist das die Modifikation an ESP durch den ADD-Befehl erst dann sichtbar wird wenn alle vorangegangenen Befehle ESP gelesen haben (der eigentliche Speicherzugriff muss deswegen noch nicht fertig sein).


@Tufelix:
mach Deine CPU erst mal so einfach wie irgend möglich, die komplexeren Dinge kommen später (und Performance noch viel später).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #88 am: 05. January 2013, 19:26 »
Soll das heißen das es im User-Mode nichts gibt was Dich mal interessieren würde? ;)
Im Moment würde mir das tatsächlich schwerfallen, was zu benennen. Vielleicht muss ich doch mal einen funktionierenden Compiler schreiben, einfach um das auch mal getan zu haben, aber auch dazu hält sich die Motivation in Grenzen. Aber damals wie heute gibt es keinen Grund, warum ich das dann in C machen sollte. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #89 am: 05. January 2013, 20:26 »
Naja war nur so ne Idee...nun ich hab jetzt mal eine CPU gezeichnet
Sieht gut aus.

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #90 am: 06. January 2013, 00:13 »
Na dann werd ich wohl den Stackpointer(hab in jetzt "ebp" genannt)  streichen und nur dem Framepointer behalten. Nun dann wird man aber 2 Befehle fürs Popen oder Pushen brauchen...(zunächst muss die adresse modifiziert und dann der wert aus bzw in den Ram geladen werden.) Hab mir deswegen überlegt das man ja über Befehle kurzzeitig 1 bestimmten wert im Adressrechenwerk dazu zählen bzw. abziehen könnte.

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #91 am: 06. January 2013, 15:24 »
FLOATing-Point soll bei mir INTegraler (...) Bestandteil der CPU sein
Klingt ja interessant. :-D
http://forum.lowlevel.eu/index.php?topic=3137.0 nicht das die neuen Posts da jetzt in den Hintergrund gedrängt werden
« Letzte Änderung: 06. January 2013, 16:09 von Martin Erhardt »

LittleFox

  • Beiträge: 305
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #92 am: 06. January 2013, 16:04 »
FLOATing-Point soll bei mir INTegraler (...) Bestandteil der CPU sein

Zitate! :D

Sorry für OT ...

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #93 am: 06. January 2013, 17:03 »
Na dann werd ich wohl den Stackpointer(hab in jetzt "ebp" genannt)  streichen und nur dem Framepointer behalten.
Ob du nun sagst, dass dein Stackpointer "ebp" oder "R15" heißt, ist egal. Der wichtige Unterschied ist, ob es nur einen einzigen Stackpointer gibt (macht die Verdrahtung einfacher) oder ob jedes Register ein Stackpointer sein kann (flexibler, aber mehr Aufwand). Für den Framepointer gilt das gleiche.

Bedenke, dass du - wenn du Interrupts unterstützen möchtest - im Interrupthandler alle Register sichern können musst, d.h. da stehen dann viele PUSHs und POPs hintereinander.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #94 am: 07. January 2013, 13:44 »
Hallo,


Der Frame-Pointer ist eher eine reine Software-Angelegenheit für Hochsprachen-Compiler aber er sollte trotzdem vorhanden sein und flexibles Adressieren mit positiven und negativen Displacements ermöglichen.

... im Interrupthandler alle Register sichern können musst ...
Dazu gehören auch die Flags (deswegen hab ich die einfach in ein normales Register gelegt) und alles andere was zum Zustand eines Threads/Task gehört. Dieses Sichern und Wiederherstellen muss übrigens Zerstörungsfrei sein (darf den zu sichernden/wiederherzustellenden Zustand nicht beeinflussen).


Floating-Point soll bei mir integraler (aber optionaler) Bestandteil der CPU sein
Damit ist gemeint das bei meiner CPU die Floating-Point-Befehle u.a. die selben Flags benutzen so das man z.B. nach einem FSUB die selben bedingten Sprünge nutzen kann um das Ergebnis auszuwerten wie nach einem SUB (reine CMP/FCMP wird es auf meiner CPU nicht geben so das man für Vergleiche immer SUB/FSUB nutzen und ein Register verschwenden muss). Auch bedeutet dass das z.B. IDIV das selbe "Divide-by-Zero"-Flag setzt (und damit auch die selbe Exception auslösen kann) wie FDIV um diese Art von Fehler zu signalisieren. Dazu kommt der Vorteil das der Scheduler im OS-Kernel nur einen Register-Satz sichern/wiederherstellen muss um den Zustand eines Threads immer komplett verwalten zu können.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #95 am: 07. January 2013, 19:09 »
Mhh die Interrupts hab ich ganz vergessen...naja dann werd ich ein paar mehr Controlleitungen im Bussystem für die I/O Ports-Interrupts und ein Interrupt-Enable-Flag im CPU einbauen. Was ich nicht ganz verstehe, ist wie die Registerinhalte gesichert werden. Dazu muss ja ne Subroutine aufgerufen werden und das verändert dann den wert im Programm-counter. Auserdem werd ich dabei bleiben das es nur einen einzigen Stackpointer, Framepointer und Adresspointer gibt.

Svenska

  • Beiträge: 1 784
    • Profil anzeigen
Gespeichert
« Antwort #96 am: 07. January 2013, 19:17 »
Was ich nicht ganz verstehe, ist wie die Registerinhalte gesichert werden. Dazu muss ja ne Subroutine aufgerufen werden und das verändert dann den wert im Programm-counter.
Du musst in der Lage sein, bei einem Interrupt ein "PUSH Program Counter; JUMP Interrupt Handler" auszuführen. Den Rest kannst du dann mit Software erschlagen. Praktisch reicht auch ein einziger Interrupt, wenn du einen Interrupt-Controller benutzen möchtest.

Gruß,
Svenska

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #97 am: 07. January 2013, 19:58 »
und ein CALL ist ein "PUSH Programcounter; JUMP Zieladresse". Den Programmcounter musst Du niemals mit einem explizitem PUSH-Befehl sichern können aber wiederherstellen ist erforderlich, also ein "POP Programmcounter" (um dann an der neuen Adresse den nächsten Befehl zu holen), das nennt sich dann RET.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #98 am: 07. January 2013, 21:44 »
Hm, nett ja, aber notwendig? Warum nicht statt "pop $pc" nicht ein "ld $sp, $r1; jmp $r1"? (Pseudo-Assembler fdS! :-D)

Und bei einem Interrupt kann man den Program Counter auch in ein Kernel-Mode-Register speichern statt auf den möglicherweise kaputten Stack zu pushen. Oder willst du einen automagischen Stackwechsel machen wie bei x86?
« Letzte Änderung: 07. January 2013, 21:47 von kevin »
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #99 am: 07. January 2013, 22:10 »
Hallo,


Warum nicht statt "pop $pc" nicht ein "ld $sp, $r1; jmp $r1"?
Weil damit r1 kaputt gemacht wird. Diese Art von RET ist nicht zerstörungsfrei. Das ist sowohl bei Funktionen als auch bei Interrupt-Handlern ein Problem es sei denn Du definierst r1 zur alleinigen Verwendung für diesen Zweck (dann darf r1 auch nie manuell gesichert/wiederhergestellt werden).

Und bei einem Interrupt kann man den Program Counter auch in ein Kernel-Mode-Register speichern statt auf den möglicherweise kaputten Stack zu pushen.
Das ist zwar ein gutes Argument das für eine richtige CPU in jeden Fall beachtet werden muss aber für ein Test-Design zum üben ist sowas IMHO noch nicht erforderlich. Solange die CPU von Tufelix nicht zwischen User-Mode und System-Mode unterscheidet reicht es wenn Interrupt-Handler genau so behandelt werden wie Funktionen (nur mit dem Unterschied das beim Interrupt-Handler alle Register callee-save / nonvolatile sind, dafür gibt es nur ein RET und nicht RET und IRET) auch wenn das ein paar grundlegende Sicherheitsrisiken hat die man bei einer richtigen General-Purpose-CPU nicht tolerieren kann.

Wenn ein Interrupt oder eine Exception nicht auf den Speicher zugreifen soll (was grundsätzlich eine gute Idee ist) benötigt man dafür in jedem Fall für jeden System-Mode einen eigenen Programm-Counter (siehe ARM), wenn es für den System-Mode nur einen einzigen Programm-Counter gibt bedeutet dass das Syscall/Exceptions/Interrupts nicht kaskadierbar sind (auf meine CPU trifft das zu) wohingegen es ja gerade der große Vorteil von x86 ist das dort quasi beliebig kaskadiert werden kann (im RM und im PM, solange auf dem Stack noch Platz ist).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

 

Einloggen