Lowlevel

OffZone => Offtopic => Thema gestartet von: Svenska am 17. March 2010, 23:57

Titel: Eigene CPU
Beitrag von: Svenska am 17. March 2010, 23:57
Hallo,

vor langer Zeit gab es ja mal den Thread "Verrückter als wir...?", den ich hiermit neu erstehen lassen möchte.

Wer baut seine eigene CPU und warum? Und wie? Welche Designziele sind vorgegeben?

Da erik.vikinger ein paar Andeutungen gemacht hat, sind wir schonmal zwei mit solchen Plänen.

Für meine Architektur (ich werde die CPU wohl nicht vom Gesamtsystem trennen) habe ich mir folgende Designziele überlegt:
- in TTL realisierbar (das ist das Endziel)
- nahezu 1 MIPS/MHz (klassisch RISC, macht die Implementation einfacher, da ich die Befehle einzeln implementieren möchte)
- Befehlssatz ähnlich MIPS, aber mit MAC-Befehl (Multiply & Accumulate)
- mindestens 16 MB RAM möglich (weniger ist doof)
- wahrscheinlich 12/24 Bit Busbreite
- keine EPROMs zur ALU-Realisierung

Warum? Ich möchte gerne mein eigenes System zum Anfassen haben. Hat den Vorteil, dass man immer sagen kann "ich könnte, wenn ich wollte... da ist ein Beispiel" :-P Außerdem lernt man so viel mehr über Schaltungsentwicklung und Rechnerarchitekturen, als man vorher wollte.

Wie? Naja, derzeit entwickle ich halt ein bisschen den Befehlssatz, immer mal wieder, und habe so quasi-diffuse Vorstellungen, wie man gewisse Teile implementieren kann. Bevor ich aber Platinen ätze, möchte ich das ganze erst noch simulieren (möglichst in einem FPGA), da müsste ich mich aber auch erst einarbeiten. Zur RAM-Adressierung schwebt mir zZt eine Art Banking-System vor, quasi Segmentierung in Hardware.

Als Betriebssystem würde ich mich bevorzugt nach POSIX richten, als Compiler-Backend eventuell einen LLVM umsetzen. Da habe ich gehört, dass es recht einfach sein soll. Ist aber alles noch Zukunftsmusik.

Meldet euch zu Wort!

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: Cool-Andy am 18. March 2010, 06:05
http://mycpu.eu/
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.
Titel: Re: Eigene CPU
Beitrag von: MNemo am 18. March 2010, 18:47
http://mycpu.eu/
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.

Jo. AFAIK war das auch der Auslöser für den erwähnten Thread "Verrückter als wir…?"
Titel: Re: Eigene CPU
Beitrag von: Svenska am 18. March 2010, 19:18
Ich wollte den Link und http://www.homebrewcpu.com/ noch anfügen, habs dann aber vergessen. Letzteres finde ich noch heftiger, aber die CPU ist wesentlich komplexer aufgebaut. Ebenfalls in TTL aufgebaut. myCPU nutzt definitiv EPROMs für die ALU, bei Magic-1 weiß ich es spontan nicht, glaube aber ja.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: chris12 am 18. March 2010, 19:19
ich hab auch meine eigene cpu, sogar 4 ums genau zu nerhmen ;)
ein einhalb in logisim und diese dann vollständig in c# umgesetzt als emulatoren, ich weiß sind keine echten hardware cpu's, aber ich find das schon lustig
für die letztere (xcpu) screibe ich btw grade an einem assembler und an einem compiler (xasm & xcomp) ^^
zur frage warum kann ich nichts sagen, das war, glaub ich, genauso wie bei meinem os, einfach aus spaß ...
Titel: Re: Eigene CPU
Beitrag von: Cool-Andy am 18. March 2010, 21:50
http://mycpu.eu/
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.

Jo. AFAIK war das auch der Auslöser für den erwähnten Thread "Verrückter als wir…?"

Sorry, den Thread habe ich nicht gelesen! mycpu.eu war nur das was mir Tante Google nahe legte aufzurufen.  :wink:
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 19. March 2010, 16:02
Hallo,


Meldet euch zu Wort!
okay, dann melde ich mich auch mal (endlich) zu Wort:

Einige haben es ja schon mitbekommen das ich ebenfalls eine CPU entwickle. Dabei verfolge ich allerdings ein paar Ziele die nicht so ganz im Rahmen des üblichen liegen.
Die Idee zu dieser CPU/Plattform-Architektur ist etwa 2007 entstanden. Ich hab bestimmt 2 Jahre nur über dem Befehlssatz und der Plattform-Architektur gebrütet bis ich mich vor gut einem Jahr dazu entschlossen habe einen Simulator zu programmieren. Der Simulator kann auch schon einiges, aber noch längst nicht alles. Da der Befehlssatz jetzt einigermaßen fest ist, ich sehe zumindest momentan keine Notwendigkeit mehr noch mal größere Änderungen vornehmen zu müssen, hab ich mich entschlossen als nächstes einen Assembler zu programmieren (das ist meine aktuelle Tätigkeit an diesem Projekt) damit ich den Code für den Simulator nicht immer per Hex-Code eingeben muss (was dank der krummen Bit-Offsets in den Befehls-Paketen auch echt bescheiden ist). Sobald der Assembler ordentlich funktioniert werde ich auch wieder am Simulator weiter arbeiten. Sobald der Simulator einigermaßen funktioniert will ich dann am OS anfangen, wie weit ich das ohne einen C/C++-Compiler treiben möchte weiß ich noch nicht. Wenn das alles ordentlich läuft will ich meine Plattform in mehreren FPGAs implementieren. Dafür existieren auch bereits verschiedene VHDL-Bruchstücke, von dehnen ein paar zumindest in der Simulation schon ganz gut arbeiten.

Als OS möchte ich einen schlanken Micro-Kernel implementieren der außer Speicherverwaltung, Prozess/Thread-Management, Scheduling, Timing und IPC kaum etwas enthält. Das IPC-Konzept hab ich ja schon mal vorgestellt.



- in TTL realisierbar (das ist das Endziel)
Das dürfte teuer werden. Ordentliche FPGA-Starter-Kits, speziel für Soft-CPU-Entwicklung, gibt es bereits ab gut 250 Euronen und da ist dann ordentlich DRAM und Flash zusammen mit ein paar Schnittstellen (manchmal sogar Ethernet) drauf.

- Befehlssatz ähnlich MIPS, aber mit MAC-Befehl
Also richtige 3 Operanden Befehle möchte ich nicht mit TTL realisieren müssen. Da solltest Du Dich eher an CISC halten und versuchen komplexe Befehle seriell einzulesen und zu verarbeiten. Aber wenn Du wirklich RISC möchtest dann nur zu, ist eben eine Herausforderung.

- keine EPROMs zur ALU-Realisierung
Das ist zwar löblich, macht es Dir aber nur noch schwieriger.

Ich möchte gerne mein eigenes System zum Anfassen haben. .... Außerdem lernt man so viel mehr über Schaltungsentwicklung und Rechnerarchitekturen, als man vorher wollte.
Zwei sehr gute Gründe. Sind bei mir die selben. :-D

Wie? Naja, derzeit entwickle ich halt ein bisschen den Befehlssatz, immer mal wieder, und habe so quasi-diffuse Vorstellungen, wie man gewisse Teile implementieren kann.
Das wirst Du dann aber mal konkretisieren müssen. Versuche einfach mal ein paar kleine Algorithmen händisch in Assembler-Code für Deine CPU umzusetzen um ein Gefühl dafür zu bekommen ob es noch Stellen gibt wo es möglicherweise hackt. Ich empfehle Dir die OpCodes in einer Tabelle zu verwalten, so behält man einen guten Überblick welche Bit-Kombinationen schon belegt sind und welche noch frei sind.
Auf wie vielen CPU-Architekturen kannst Du denn (fließend) Assembler? Man sollte IMHO schon etwas Erfahrung in der Benutzung mehrerer verschiedener CPU-Architekturen haben um nicht gleich zu viele Fehler zu machen. Wenn Du erst später beim programmieren von Programmen für Deine CPU merkst das der Befehlssatz ein paar Probleme hat wirst Du Dich sicher ziemlich ärgern weil Du dann entscheiden darfst ob Du noch mal von vorne anfängst oder ob Du mit dem Problem irgendwie leben willst. Das ist IMHO die ekligste Sorte von Entscheidungen die man treffen muss.

(möglichst in einem FPGA), da müsste ich mich aber auch erst einarbeiten.
Das ist aber kein leichtes Thema, betrachte es als Herausforderung.

Zur RAM-Adressierung schwebt mir zZt eine Art Banking-System vor, quasi Segmentierung in Hardware.
Wie stellst Du Dir das vor? So ähnlich wie auf einigen Mikro-Controllern? Oder eher in Richtung x86 Real-Mode? Compilerunterstützung ist damit übrigens ein ernstes Problem, (fast) alle heutigen Compiler gehen von Flat-Memory aus. Bezüglich meiner Segmentierung hab ich mich auch sehr intensiv umgeschaut und bin heute der Meinung das der LLVM da noch der interessanteste Kandidat ist (zwischenzeitlich hatte ich mal den gcc favorisiert), ich denke ich kann dem LLVM noch am ehesten meine Segmente beibringen, er arbeitet (fast) nie direkt mit Pointern so das ich diese spezielle Komplexität wohl gut verbergen kann.



ich hab auch meine eigene cpu, sogar 4 ums genau zu nerhmen ;)
ein einhalb in logisim und diese dann vollständig in c# umgesetzt als emulatoren, ich weiß sind keine echten hardware cpu's, aber ich find das schon lustig
für die letztere (xcpu) screibe ich btw grade an einem assembler und an einem compiler (xasm & xcomp)
Kannst Du dazu mal was genaueres schreiben? Ist bestimmt interessant.



Wenn jemand Fragen, Anregungen oder Kritik zu meinem Konzept hat dann nur zu.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Cool-Andy am 19. March 2010, 16:11
 :-o Boa! Hast du den schon ne Website für dein Projekt?
Titel: Re: Eigene CPU
Beitrag von: Svenska am 19. March 2010, 18:57
Hallo,

das war eine ziemlich ausführliche Beschreibung des Systems. Und ich merke den Unterschied zu meiner Idee. :-)

Ein Designziel bei mir ist, wie gesagt, die Implementierbarkeit in TTL. Dass das nicht billig wird, ist mir klar, aber dafür ist es ein Hobby. :-D Wahrscheinlich werde ich aber trotzdem eine FPGA-Lösung entwickeln, vermutlich wird ein CPLD aber nicht ausreichen. :-(

Ich möchte mein System recht einfach stricken, also mich nicht an x86 orientieren, sondern eher an kleinen 8-Bit-Systemen und die "etwas" aufbohren. Performance ist bei mir zweitrangig und wird hinter die Implementierbarkeit gestellt.

Der Befehlssatz soll nur die wirklich notwendigen Befehle bereitstellen (plus einen MAC-Befehl, den halte ich für nützlich) und auch keine 3-Operanden-Form haben; das ganze System wird sehr Akkumulator-zentrisch gebaut werden (wobei ich noch nicht weiß, ob ich ein oder zwei Akkumulatorregister haben werde). Hardwareseitig hätte ich bevorzugt einen Instruktionsbus, auf dem ich die einzelnen Instruktionen verarbeiten lasse. Um den Aufwand zu begrenzen, werde ich nur 16 (evtl. 32) verschiedene Befehle zulassen, denen dann in der Instruktion selbst noch Daten (=Registeradressen) folgen. Somit könnte ich jeden Befehl (charakterisiert durch die ersten 4/5 Bits) auf einer Platine realisieren; bei Logikbefehlen möglicherweise noch mehr.

Alle Instruktionen sollen in einem Taktzyklus abgearbeitet werden, mir schweben etwa 3-5 MHz (bei einer Gattergeschwindigkeit von ~20-50 MHz, im FPGA natürlich wesentlich mehr) vor. Das sollte realistisch machbar sein. Um die Implementation zu vereinfachen, gibt es nur zwei Befehle für den Speicherzugriff (LOAD/STORE) und nur einen Befehl für Konstanten (LOAD IMMEDIATE). Diese Befehle werden vermutlich zwei (oder drei) Takte benötigen, um weitere Datenworte einzulesen; alle anderen Befehle sollen in einem Takt und ausschließlich mit Registern abgearbeitet werden.

Die Speicheransteuerung wird ebenso simpel sein. Da ich mind. 24-Bit-Adressen (=16 MB) auf einer schmaleren Adress-/Datenbreite haben möchte, schwebt mir ein Banking-System wie aus Frühzeiten vor; ich werde also Speicherblöcke in den Adressraum einblenden. Die Blockgröße und die Anzahl der Blöcke stehen aber noch nicht fest.

Allein diese Speicheraufteilung erinnert stark an Overlay-Technik, wo mir jeder Compiler was husten wird. Mit ein bisschen Spielerei an den Adressleitungen sollte ich ein flacher Speicherraum aber simulieren lassen, wenn auch der Overhead nicht gerade leicht sein wird. Im Prinzip handelt es sich aber um eine Segmentierung mit Segmenten festgelegter Größe (und Zugriffe auf andere Segmente kosten viel Performance), daher bin ich schon an Segmentierung interessiert.

Paging, Speicherschutz und SMP sind in der Idee nicht vorgesehen.

Als Bussystem wird wohl der Speicherbus herhalten müssen, dann hätte ich Speicherbänke, die nicht mit Speicher versehen sind, sondern direkt die Hardware ansprechen. Damit sollte sich relativ einfach ein I2C- oder SPI-Interface basteln lassen, welches als I/O dient. Wenn ich den Adressraum in vier frei mapbare Bänke zerlege, lässt sich das auch recht performant ansprechen.

Das alles sind aber nur Gedanken zur Umsetzung einiger Details, ein Gesamtsystem habe ich noch nicht entworfen. Beispielsweise steigt der Lötaufwand mit steigender Busbreite stark an, aber ein zu schmaler Bus schränkt den Adressraum (und damit die sinnvolle Programmierung) stark ein. Da einen guten Kompromiss zu finden ist schwierig bis unmöglich. Es wird mir wahrscheinlich nicht gelingen, einen TCP/IP-Stack ohne Würgereize in 1K oder 4K-Overlays zu zerlegen. Andererseits sind 256K-Blöcke (18 Bit) auf einer 12-Bit-Architektur beschissen zu adressieren...

Das OS wird natürlich in diesen Beschränkungen auch leben müssen, ich strebe aber grundsätzliche POSIX-Kompatiblität an. Der Kernel selbst wird vermutlich außer Speicher- und Taskverwaltung nur eine Filesystemschnittstelle bieten, über die der Hardwarezugriff möglich wird. Allerdings sind das nur Gedanken, die ich bisher noch weg schiebe.

Ich würde auch gerne von chris12 hören wollen, wie er seine CPUs gestrickt hat!

Was sagt ihr zu dem Grobkonzept erstmal?

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 19. March 2010, 20:16
Hallo,


das war eine ziemlich ausführliche Beschreibung des Systems.
Das war ein sehr grober Umriss meines Konzepts. :-D Da fehlen noch etliche Dinge.

Wahrscheinlich werde ich aber trotzdem eine FPGA-Lösung entwickeln, vermutlich wird ein CPLD aber nicht ausreichen. :-(
Also für einen CPLD müsste Deine CPU schon sehr primitiv sein. Hast Du Dich schon für eine FPGA-Serie entschieden? Ich persönlich möchte auf die Spartan 6 von Xilinx setzen, aber die LXT-Variante. Zur Verkabelung der einzelnen Platinen untereinander will ich dann gekreuzte SATA-Kabel benutzen, die sind relativ billig und trotzdem gut.

Ich möchte mein System recht einfach stricken, also mich nicht an x86 orientieren,
Der Original i8086 war schon recht simpel und der i8088 hatte sogar nur einen 8 Bit-Datenbus.

sondern eher an kleinen 8-Bit-Systemen und die "etwas" aufbohren.
Eine interessante Idee, da gibt es viele Konzepte von dehnen Du was abschauen kannst.
Kennst Du die 8 Bit AVRs von Atmel? Die habe nur 8 Bit Register (und gleich 32 Stück) können aber zur Speicheradressierung 2 davon zusammenfassen also 64 kBytes adressieren. Du könntest z.B. 16 Stück von 16 Bit Registern haben und immer 8 Paare für die flache Adressierung nutzen. Das bekommst Du auch dem Compiler ganz einfach beigebracht. Lade Dir bei Atmel.com einfach mal die Core-Spec für die 8 Bitter runter und ließ Dir das durch, wenn das Konzept interessant für Dich ist und/oder Du fragen hast kannst Du Dich gerne bei mir melden.

Performance ist bei mir zweitrangig und wird hinter die Implementierbarkeit gestellt.
Also für mich ist hohe Performance schon ganz klar ein Design-Ziel. Nur die Funktionalität, welche ich verwirklichen möchte, ist mir wichtiger.

Der Befehlssatz soll nur die wirklich notwendigen Befehle bereitstellen (plus einen MAC-Befehl, den halte ich für nützlich) und auch keine 3-Operanden-Form haben; das ganze System wird sehr Akkumulator-zentrisch gebaut werden (wobei ich noch nicht weiß, ob ich ein oder zwei Akkumulatorregister haben werde).
Du hattest von MIPS geredet, daher bin ich von festen 32Bit-Befehlen mit 3 (manche Befehle haben auch 4) Operanden ausgegangen, so wie auf den meisten RISC-CPUs (Alpha, MicroBlaze, PowerPC, Sparc usw.). Erzähl doch mal was genaueres über die Befehls-Architektur. Sollen die Befehle z.B. alle die gleiche Größe haben?

Alle Instruktionen sollen in einem Taktzyklus abgearbeitet werden, mir schweben etwa 3-5 MHz (bei einer Gattergeschwindigkeit von ~20-50 MHz, im FPGA natürlich wesentlich mehr) vor. Das sollte realistisch machbar sein.
Single-Cycle sollte Dir eine Zweistufige-Pipeline ermöglichen (so wie bei den 8 Bit AVRs). Zur Frequenz kann ich nichts sagen, ich denke das ist einer der Grüde warum der Macher von mycpu.eu sich einen Gate-Level-Simulator geschrieben hat der auch die Laufzeiten der Signale und der TTL-Bauteile berücksichtigt.

Um die Implementation zu vereinfachen, gibt es nur zwei Befehle für den Speicherzugriff (LOAD/STORE) und nur einen Befehl für Konstanten (LOAD IMMEDIATE).
Das macht es auch dem Compiler recht einfach. Ich hab das ähnlich vorgesehen nur das ich noch viele Rechen-Befehle hab die auch mit Immediates umgehen können und auch komplexere Adressierungsarten für die Speicherzugriffe. Zusätzlich hab ich noch Speicherzugriffs-Befehle für mehrere Register vorgesehen (so wie LDM und STM bei ARM).

... schwebt mir ein Banking-System wie aus Frühzeiten vor; ich werde also Speicherblöcke in den Adressraum einblenden. Die Blockgröße und die Anzahl der Blöcke stehen aber noch nicht fest.
Allein diese Speicheraufteilung erinnert stark an Overlay-Technik, wo mir jeder Compiler was husten wird.
Oh ja, damit bekommst Du sicher Probleme (nicht nur mit dem Compiler). Von richtigem Banking würde ich persönlich eher abraten.

Paging, Speicherschutz und SMP sind in der Idee nicht vorgesehen.
Hatte ich bei einem TTL-Projekt auch nicht wirklich erwartet. :wink:

Es wird mir wahrscheinlich nicht gelingen, einen TCP/IP-Stack ohne Würgereize in 1K oder 4K-Overlays zu zerlegen.
Ein Grund mehr vom Banking Abstand zu nehmen. 1 oder 4 kBytes sich wirklich zu wenig 16 kBytes sollten es schon sein.

.... Allerdings sind das nur Gedanken, die ich bisher noch weg schiebe.
Ja, sowas kommt erst wenn Du Dich auf eine Befehls- und Plattform-Architektur festgelegt hast. Natürlich musst Du die gewünschten OS-Konzepte immer mit im Blick behalten.


Hast du den schon ne Website für dein Projekt?
Nein. Ich bin mir noch nicht mal beim Namen sicher. Was mir auch noch fehlt ist eine geeignete Lizenz. Bis jetzt sieht für mich nur die Qt-Lizenz interessant aus.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 20. March 2010, 00:25
Wahrscheinlich werde ich aber trotzdem eine FPGA-Lösung entwickeln, vermutlich wird ein CPLD aber nicht ausreichen. :-(
Also für einen CPLD müsste Deine CPU schon sehr primitiv sein. Hast Du Dich schon für eine FPGA-Serie entschieden? ...
Überhaupt nicht. Die Begrenzung liegt da im Geld (und in den anderen Projekten, die nebenher noch so laufen), daher hab ich mich damit noch nicht so wirklich beschäftigt.

Ich möchte mein System recht einfach stricken, also mich nicht an x86 orientieren,
Der Original i8086 war schon recht simpel und der i8088 hatte sogar nur einen 8 Bit-Datenbus.
Naja, ich werde CPU und System bei mir nicht trennen. Außerdem ist der 8088 intern relativ komplex... ich möchte den ja nirgends einbauen, sondern die CPU selbst entwickeln.

Kennst Du die 8 Bit AVRs von Atmel? Die habe nur 8 Bit Register (und gleich 32 Stück) können aber zur Speicheradressierung 2 davon zusammenfassen also 64 kBytes adressieren. Du könntest z.B. 16 Stück von 16 Bit Registern haben und immer 8 Paare für die flache Adressierung nutzen.
Die AVR kenne ich, hab ich schon programmiert. Register sind aber in TTL sehr, sehr umständlich und teuer zu implementieren, weswegen ich möglichst wenig davon haben möchte. Ich denke mit ~<10 Registern je 8 bzw. 12 Bit auskommen zu können, muss mir da aber noch mehr Gedanken machen.

Der Befehlssatz...
Du hattest von MIPS geredet, daher bin ich von festen 32Bit-Befehlen mit 3 (manche Befehle haben auch 4) Operanden ausgegangen, so wie auf den meisten RISC-CPUs (Alpha, MicroBlaze, PowerPC, Sparc usw.). Erzähl doch mal was genaueres über die Befehls-Architektur. Sollen die Befehle z.B. alle die gleiche Größe haben?
Ich dachte eher an den Befehlssatz MIPS I, aber nicht dessen encodierung. Alle Befehle sollen die gleiche Länge haben (eine Datenbreite), um in einem Takt auch geladen werden zu können. Einzige Ausnahme werden LOAD, STORE und LOAD IMMEDIATE sein müssen; wahrscheinlich werde ich dafür eine Art Zustandsautomat realisieren.

Der Aufbau ist möglicherweise:
- 4 Bit Instruktion (=Instruktionsplatine)
- 1 Bit Akkumulatorauswahl (=Ziel)
- 3 Bit Registerauswahl (=Quelle)

Wahrscheinlich werde ich aber 5 Bit Instruktion vorsehen müssen, und bei einer 12-Bit-Architektur habe ich auch wesentlich mehr Spielraum für die Codierung. Das heißt auch, es wird unbenutzte Opcodes geben, je nachdem, wieviel Arbeit ich auf eine Europlatine stecken kann.

Alle Instruktionen sollen in einem Taktzyklus abgearbeitet werden, mir schweben etwa 3-5 MHz (bei einer Gattergeschwindigkeit von ~20-50 MHz, im FPGA natürlich wesentlich mehr) vor. Das sollte realistisch machbar sein.
Single-Cycle sollte Dir eine Zweistufige-Pipeline ermöglichen (so wie bei den 8 Bit AVRs). Zur Frequenz kann ich nichts sagen, ich denke das ist einer der Grüde warum der Macher von mycpu.eu sich einen Gate-Level-Simulator geschrieben hat der auch die Laufzeiten der Signale und der TTL-Bauteile berücksichtigt.
Ich werde wohl keine Pipeline implementieren, das treibt den Aufwand in die Höhe. Höchstens eine Pipeline zwischen "Lesen" und "Ausführen", wobei ich (a) bei Sprüngen dem Compiler das NOP überlasse und (b) nichtmal einen Ahnungsansatz habe, wie ich länger andauernde Befehle in diesen Zyklus einbinden könnte. Es sei denn, ich halte die CPU und damit die Pipeline an...

Einen timingkritischen Simulator werde ich wohl nicht bauen, sondern den FPGA als Testbett nehmen; wenn ich die kritischen Pfade auszähle und gleichartige Gatter verbaue, sollte sich das einigermaßen ausrechnen lassen. Dabei ist es für mich eine Pfadlänge von 10, was mir vorschwebt. Allerdings ist das eher ne diffuse Geschichte.

... schwebt mir ein Banking-System wie aus Frühzeiten vor; ich werde also Speicherblöcke in den Adressraum einblenden. Die Blockgröße und die Anzahl der Blöcke stehen aber noch nicht fest.
Allein diese Speicheraufteilung erinnert stark an Overlay-Technik, wo mir jeder Compiler was husten wird.
Oh ja, damit bekommst Du sicher Probleme (nicht nur mit dem Compiler). Von richtigem Banking würde ich persönlich eher abraten.
Es vereinfacht die Implementation aber ungemein, da ich die gesamte "MMU" in zwei-drei Gatter verlagern kann und trotzdem die Adressleitungen einspare. Das ist der diskreten Realisierung geschuldet; ich kann keine 32 Leitungen eines Bussystems durch mehrere Platinen schaufeln und trotzdem noch Platz für Logik haben.

Wenn ich dem Compiler einen virtuellen Zeiger über mehr als den Adressraum gebe und ihm mitteile, dass er die Bank vorher extra belegen muss, dann sollte ich aber lauffähigen (und krass unperformanten) Code hinkriegen. Flatmem-Simulation eben. Vielleicht muss ich später auch einen C-Ableger basteln, wo ich dem Compiler die RAM-Verwaltung abnehme und den Teil per Hand machen muss.

Es wird mir wahrscheinlich nicht gelingen, einen TCP/IP-Stack ohne Würgereize in 1K oder 4K-Overlays zu zerlegen.
Ein Grund mehr vom Banking Abstand zu nehmen. 1 oder 4 kBytes sich wirklich zu wenig 16 kBytes sollten es schon sein.
Ich überlege, wie ich einen 1 MB Adressraum in vier 256K-Bänke zerlegen kann und das trotzdem in eine Busbreite von 12/24-Bit kriege. Ohne sinnlos Bits zu verschwenden. Zumindest die Größenordnung schwebt mir vor. Außerdem sind 64 Bänke (davon vllt. 48 nutzbar) genug, um die Organisation für das OS einfach zu halten. Jedes Programm kriegt dann eine RAM-Bank für sich, eventuell für Code/Daten/Stack je eine.

.... Allerdings sind das nur Gedanken, die ich bisher noch weg schiebe.
Ja, sowas kommt erst wenn Du Dich auf eine Befehls- und Plattform-Architektur festgelegt hast. Natürlich musst Du die gewünschten OS-Konzepte immer mit im Blick behalten.
Das erinnert mich an die IPC-Architektur von AmigaOS (Mikrokernel) - einfach einen JUMP in den Kernel. Wenn die Hardware eh keinen Speicherschutz bietet, ist das eine schnelle Variante mit den Vorteilen eines Mikrokernels. Prinzipiell orientiere ich das OS an der Hardware statt umgekehrt.

Der Befehlssatz selbst ist ja quasi-festgelegt, nur die Codierung noch nicht. Und natürlich die Architektur außen rum, was ja die Schwierigkeit ist. Da das aber ein integriertes System ist, kann und will ich CPU, Speicher und I/O nicht trennen.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 20. March 2010, 10:00
Hallo,


Hast Du Dich schon für eine FPGA-Serie entschieden? ...
Überhaupt nicht. Die Begrenzung liegt da im Geld
Oh ja, wenn das Geld-Problem nicht wäre dann würde ich natürlich den Virtex 6 bevorzugen.

ich möchte den ja nirgends einbauen, sondern die CPU selbst entwickeln.
Richtig, ich vergaß. :-)

Register sind aber in TTL sehr, sehr umständlich und teuer zu implementieren,
Ich hab mit der 74-Irgendwas-Reihe schon lange nicht mehr gearbeitet aber ganz normale egde-getriggerte D-FlipFlops mit Enable-Eingang sind doch nun wirklich was ganz normales. Wimre gibt es da z.B. 8 Bit in einem 20 Pin-Gehäuse. Aufwändiger stelle ich mir das Problem der Selektierung eines bestimmten Registers vor um dessen Daten z.B. an die ALU zu übergeben.

Der Aufbau ist möglicherweise:
- 4 Bit Instruktion (=Instruktionsplatine)
- 1 Bit Akkumulatorauswahl (=Ziel)
- 3 Bit Registerauswahl (=Quelle)
Das sieht doch schon gut aus. Wobei 8 Bit eventuell wirklich etwas wenig sind. Sieh Dir mal das http://www.opencores.org/project,zpu an, diese CPU kommt fast ganz ohne Register aus und der Befehlssatz scheint auch sehr simpel zu sein. 442 LUTs von einem Spartan 3 ist so wenig das sollte sich mit vertretbarem Aufwand in TTL realisieren lassen. Bestimmt kann man dieses Konzept noch etwas nach unten skalieren wenn man keine richtige 32 Bit CPU will.

Wahrscheinlich werde ich aber 5 Bit Instruktion vorsehen müssen, und bei einer 12-Bit-Architektur habe ich auch wesentlich mehr Spielraum für die Codierung.
Du weißt das Du mit einer 12 Bit Architektur zu nichts auf der Welt kompatibel bist? Nichtmal zu den üblichen C-Compilern. Oder Du verschwendest bei den Daten immer 4 Bit. Vielleicht wäre für Dich auch eine Harvard-Architektur (so wie die 8 Bit AVRs) interessant.

.... dann sollte ich aber lauffähigen (und krass unperformanten) Code hinkriegen. Flatmem-Simulation eben.
Das erinnert mich an den Output von C-Compilern für den x86 Real-Mode bei großen Programmen, da musste auch für fast jeden zweiten Speicherzugriff ein Segmentregister geladen werden (es sind ja nur DS und ES frei verfügbar). Performance ist wirklich was anderes. Ich will ja auch Segmentierung aber da ich 16 Segmentregister vorgesehen hab und die Segmente volle Größe erreichen können denke ich mal werden solche Dinge auf meiner Architektur keine Performance kosten.

Vielleicht muss ich später auch einen C-Ableger basteln, wo ich dem Compiler die RAM-Verwaltung abnehme und den Teil per Hand machen muss.
Das würde ich mir an Deiner Stelle gut überlegen. Ich verstehe zwar nicht ganz was Dir da vorschwebt aber ich vermute das für sowas mal problemlos ein Mann-Jahr drauf geht (in Sonntag-Nachmittage umgerechnet bist Du dann ziemlich lang beschäftigt).

Prinzipiell orientiere ich das OS an der Hardware statt umgekehrt.
Also ich versuche CPU- und Plattform-Architektur mit dem OS als eine Einheit zu betrachten. Die HW soll die OS-Konzepte möglichst gut (und performant) unterstützen und das OS soll die Fähigkeiten der HW optimal ausreizen. Die HW wird bei mir nichts unterstützen was das OS nicht sinnvoll nutzen kann und das OS wird nichts voraussetzen was sich in HW nicht vernünftig umsetzen lässt.

Da das aber ein integriertes System ist, kann und will ich CPU, Speicher und I/O nicht trennen.
Da haben wir etwas unterschiedliche Design-Ziele. CPU-Architektur und Plattform-Architektur sollen bei mir nur aus SW-Sicht aus einem Guss sein, aus HW-Sicht sind es mehr oder weniger unabhängige Teil-Systeme mit einer klar definierten Schnittstelle.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: chris12 am 20. March 2010, 11:32
meine cpus sind mehr oder minder sehr simpel
die erste hab ich in logisim entwickelt und konnte ganze 16Byte Ram und 256Byte Rom ansprechen. im Rom lag der einfachheit halber der programcode und im ram wurde linear das ergebnis der aluoperation abgelegt. allerdings konnte man keine daten aus dem ram lesen, aber ich war schon happy, dass es überhaupt irgendwie funktioniert hat. die cpu, kpu mit namen, war eine 4bit cpu mit einem 16bit breiten befehlssatz. die 4bit für daten resultierten daraus, dass alle operationen in diesen 16bit einzeln untergebracht wurden, da ich noch nichts vom decodieren gehört hatte.
ein befehl war also so aufgebaut:
x x x x x x x x x x x x x x x x
| | | | | | | | | | | | | | | |
 -   - - -   -   -   -   - - -
 |     |     |   |   |     |
Addr  Addr  Alu   Reg     Data
 OP         OP   CB  CA
 
das bedeutet ein
mov r1, 4 wurde zu 0000000000100100
und leider musste aus irgenteinem grund nach jeder aluop ein null word kommen, da sonst falsche ergebnisse in den ram geschrieben werden.
btw hatte kpu auch keinerlei jmp operationen, nicht mal einen einfachen sprung, obwohl es vorgesehen war. sie hatte 3 register r0, r1 und r2, die alle allzweck waren

die weiterentwicklung war KPU Mk 2, oder einfach KPUII. diese sollte schon besser sein. sie hatte zwar immernoch die 16bit instructionen, aber konnte schon 8bit daten verarbeiten.
eine instruction war also so aufgebaut:
x x x x x x x x x x x x x x x x
| | | | | | | | | | | | | | | |
Enable   - - -   - - - - - - -
| | | |    |           |
J A  R     OP         Data
m L  e
p U  g
    | |
Write Read
aus einem mov r1, 4 wurde jetzt 0010000100000100
kpuII wurde in logisim die wirklich fertiggestellt, aber dafür hab ich meinen ersten emulator in c# geschrieben KPUIIEMU ;)
kpuII hatte 16 allzweckregister. jump operationen wie
jmp
je
jne
ja
jb
und konnte auf den ram und den rom dierekt zugreifen, zwar nur mit festwerten, aber immerhin. der programmcode lag immernoch im rom. alletdings hatte diese cpu schon einen screen port und einen keyboard port, die allerdings nie in logisim sorichtig funktioniert haben. aber dafür waren die befehle read (register) und disp (festwert | ram:[(adresse)] | rom:[(adresse)] | register) vorgesehen.

meine nächste cpu, die ich erst auf dem papier und dann in logisim umsetzen wollte, war die 140A, eine richtige multibyte 8bit cpu. daten und code lagen im ram und sogut wie alle operationen sind multibyte bishin zu 3 byte. diese cpu ist in logisim nie sorichtig fertig geworden, da ich mit probleme mit den multibyte instruktionen hatte.
die 140A hatte einen 8bit breiten adressbus und konnte somit 256byte an ram ansprechen. sie hatte 4 allzweckregister, einen ip und ein flagregister.
die instruktionen waren so aufgebaut
xxxx | xxxx | OP
-----|------|---------
0000 | 0000 | HLT
0001 | 00 # | r# = <IP+1>
0001 | 0100 | [<IP+1>] = <IP+2>
0001 | 10 # | r# = [<IP+1>]
0001 | 11 # | [<IP+1>] = r#
-----|------|---------
0111 | #1#2 | cmp r#1, r#2
0110 | 00 # | cmp r#, <IP+1>
0110 | 01 # | cmp r#, [<IP+1>]
-----|------|---------
0011 | 0000 | jmp <IP+1>
0011 | 0001 | je <IP+1>
0011 | 0010 | jne <IP+1>
0011 | 0011 | ja <IP+1>
0011 | 0100 | jb <IP+1>
0011 | 0101 | jna <IP+1>
0011 | 0110 | jnb <IP+1>
-----|------|---------
1000 | #1#2 | r#1 = r#1 + r#2
1001 | #1#2 | r#1 = r#1 - r#2
1010 | #1#2 | r#1 = r#1 * r#2
1011 | #1#2 | r#1 = r#1 / r#2
1100 | #1#2 | r#1 = r#1 & r#2
1101 | #1#2 | r#1 = r#1 | r#2
1110 | 00 # | r# = ~r#
1111 | #1#2 | r#1 = r#1 XOR r#2
wie man erkennen kann recht simpel.
flags gab es nur 3 stück: das gleichheitflag, größerflag und kleiner flag.
aus mov r0, 0xF0 wird also 00010000 11110000
einige kleinigkeiten, wie zum beispiel adressierung über register, müssen über kleinere hacks gemacht werden. wodurch dann selbstmodifizierender code entsteht.

die letzte cpu und die auch größte in meinen projekt umfang ist die XCPU, warum die so heißt weiß ich leider auch nicht.
diese cpu arbeitet mit sicherheit nicht mehr nach dem RISC prinziep sondern eher im gegenteil. sie hat einen 32 bit adressbus kann mit 32bit daten umgehen und eine ram speicherzelle hat 32 bit. somit kann diese cpu 137438953472 bit insgesamt ansprechen was  16 GB ergeben. leider kann der emulator nur ein zwanzigstel, also ca 0.8 GB, davon bereit stellen, da meine maschiene nicht mehr hergibt.
es giebt 16 register, die allzweck benutzt werden könnten, aber vom assembler für call oder stack operationen genutzt werden. die cpu beherscht diese nähmlich nicht und, desshalb habe ich diese hacks gleich in den assembler eingebaut.
die cpu kann teoretisch 2^32 in und out ports haben allerdings werden vom emulator vorerst nur 0-2 unterstützt. flags gibts 32 aber nur 4 existieren, die drei der 140A und das zero flag. interrupt, die es bei den anderen nicht gab, sollen auch implementiert werden, allerdings muss ich mir darüber nochmal gedanken machen.

zum assembler:
ich hab fast für jede meiner cpus eien eigenen assembler, in c#, geschrieben, außer für die 140A. die syntax ist für alle dem intelsyntax der x86 ähnlich, nur die register heißen anders r0-r(2, 3 oder 15 bzw 9). also mov (ziel), (quelle). bei der xcpu werden allerdings auch operationen wie add r1, r2, r3 möglich sein (r3 = r1 + r2).
der assembler für die xcpu ist noch nicht ganz fertig, aber es gibt schon labels, jmp, data anweisung, org anweisung, times anweisung und die $ anweisung.

wenn noch fragen sind beantworte ich die gerne
btw wenn ich in den rechnungen(bit -> GB) einen fehler hab korrigiert mich bitte
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 20. March 2010, 15:24
Hallo,


meine cpus sind mehr oder minder sehr simpel
Mag sein, aber immerhin tun sie was, Respekt!

.... wodurch dann selbstmodifizierender code entsteht.
Pfui, schäm Dich! :wink:
Sowas unanständiges macht man heute doch nicht mehr. AMD und Intel verkaufen uns das NX-Flag als Anti-Virus-Flag und Du erhebst modifizierbaren Code zum Design-Ziel. Wenn ich mal wirklich Langeweile hab schreib ich fix nen Virus für Deine CPU. :-D

ich hab fast für jede meiner cpus eien eigenen assembler, in c#, geschrieben
Wie viel Arbeit hat das den in etwa gemacht bzw. wie viel Quell-Code ist das ungefähr?

also mov (ziel), (quelle). .... wie add r1, r2, r3 möglich sein (r3 = r1 + r2).
Das passt irgendwie nicht zusammen, sollte es nicht "r1 = r2 + r3" heißen?

der assembler für die xcpu ist noch nicht ganz fertig, aber es gibt schon labels, jmp, data anweisung, org anweisung, times anweisung und die $ anweisung.
Wie verwaltest Du die Befehle intern? Wie löst Du die Labels auf?

Ich nutze dafür ganz einfach vector (in C++). Wenn der Code dann vollständig eingelesen ist verteile ich alles auf die einzelnen Sektionen/Segmente (.code , .const , .data usw.), alle Nicht-Code-Segmente werden dann "assembliert" damit dort alle Labels fest stehen. Anschließend werden die Code-Segmente assembliert. Dazu gibt es eine Vorrunde in der bei allen Befehlen geprüft wird wie groß sie mindestens werden damit jeder Befehl ein Initial-Offset bekommen kann. Dann soll der gesamte Code so lange immer wieder assembliert und optimiert werden bis sich das best-mögliche Ergebnis eingestellt hat (das Abbruchkriterium wird mir sicher einiges an Kopfzerbrechen bereiten). Viele Befehle können in kleinerer Ausführung benutzt werden, vor allem Sprünge müssen nur sehr selten in der 60 Bit Version (damit kann in 64 TByte Code vor und in 64 TByte Code zurück gesprungen werden) vorliegen für eine kurze Schleife, die sich vielleicht nur über 30 Assemblerbefehle erstreckt, reichen auch die 20 Bit Sprünge (damit kann noch in +/- 32 kByte Code vor und zurück gesprungen werden). Aufgrund meiner sehr komplexen Befehls-Architektur wird nicht nur der Decoder in der CPU reichlich fett werden sondern auch der Encoder (Code-Generator) im Assembler. Linken möchte ich aus diesem Grund noch vor dem Assemblieren damit der Code-Generator wirklich über das gesamte Programm optimieren kann. Das damit ein Compile-Lauf nicht gerade als schnell gilt ist mir klar (ist ja z.B. beim Itanium nicht anders), aber Ausführungsgeschwindigkeit ist mir schon ein wichtiges Kriterium.

Von der Funktionalität meiner Plattform möchte ich schon in die Nähe aktueller PCs kommen, bei der Performance kann ich mich wahrscheinlich schon bei einem Hundertstel glücklich schätzen. Ich hoffe ich erreiche im FPGA 50 MHz Takt für den CPU-Kern, damit möchte ich dann performancemäßig etwa mit einem 100 MHz 486 gleich ziehen.


@Svenska: Hast Du eigentlich schon über Interrupts (als INT-Befehl für die SW für SYSCALLs oder als IRQ von der Peripherie) nachgedacht?


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: chris12 am 20. March 2010, 18:11
Zitat von: erik.vikinger
Das passt irgendwie nicht zusammen, sollte es nicht "r1 = r2 + r3" heißen?
ist eigentlich egal. zudem noch ist es nicht im assembler implementiert, da können die operanden immernoch vertauscht werden ;)

zum selbstmodifizierenden code:
es war kein design ziel, sondern ist eine notwendigkeit aufgrund der einfachheit ;)

zu den assemblern:
kpuasm: nur 281 zeilen (könnten aber reduziert werden)
kpuIIasm: 564 zeilen in 3 dateien (könnten auch reduziert werden)
xasm: 842 zeilen (werden noch mehr werden)

zu den befehlen:
ich mach das über eine böse variante, über string split und dann mit regex vergleichen, da ich da noch nichts vom ordentlichen compilerbau/assemblerbau gehört hab.
wenn man jetzt folgenden xasm code hat
data 0x10000000, :hallo ;mov r0, :hallo
data 0x10020000, $+3 ;mov r2, $+3 )
jmp :start           ;            ) call :start
jmp $-1
start:
data 0x20310000    ;mov r1, [r0]
data 0x01110000, 0  ;out 0, r1
data 0x30100000, 1 ;add r0, 1
cmp r1, 0
jne :start
jmp r2  ;ret
hallo:
data "Hallo, Welt!",10,13, 0
wird zuerst folgender objekt code in stage 1 generiert
10000000 :hallo
10020000 $+3
40100000 :start
40100000 $-1
start:
20310000
01110000 00000000
30100000 00000001
00210000 00000000
42100000 :start
40020000
hallo:
00000048 00000061 0000006C 0000006C 0000006F 0000002C 00000020 00000057 00000065 0000006C 00000074 00000021 0000000A 0000000D 00000000
dann werden die labels eingelesen und in einer KeyValue liste abgespeichert zusammen mit ihren adressen, bei bedarf noch der org wert drauf addiert, und danach werden die labels mit ihrer adresse ersetzt.
dann hat man diesen code 10000000 00000212
10020000 00000206
40100000 00000208
40100000 00000206
20310000
01110000 00000000
30100000 00000001
00210000 00000000
42100000 00000208
40020000
00000048 00000061 0000006C 0000006C 0000006F 0000002C 00000020 00000057 00000065 0000006C 00000074 00000021 0000000A 0000000D 00000000
der wirt dann einfach in die entsprechenden hexwerte umgewandelt und in die ausgabe datei geschreiben.

zum aufwand:
je komplexer die cpu bzw deren befehlssatz, desto komplexer der assembler. und xasm ist immernoch nicht fertig.

ich hoffe ich hab deine fragen damit beantwortet
Titel: Re: Eigene CPU
Beitrag von: Svenska am 20. March 2010, 18:20
Hallo,

dass eine 12-Bit-CPU inkompatibel zur Welt ist, ist mir durchaus bewusst. Das war auch die Ursprungsidee, aus der sich alles entwickelt hat. :-o

Mit dem jetzigen Ziel habe ich eine recht hohe Ausführungsgeschwindigkeit, brauche aber viele Befehle für gewisse Aktionen. Angenommen, ich muss ständig die Bänke wechseln, das kostet jeweils 5-10 Takte zusätzlich. Ein 8086 braucht aber allein für die Adressberechnung zwischen 7 und 12 Taktzyklen plus die jeweilige Ausführungszeit des Befehls (2..17, bei MUL/DIV in die Hunderte), ein 8088 durch den 8-Bit-Datenbus (und die kleinere Prefetch-Queue) im Schnitt ein Drittel mehr.

Mit dem C-Ableger hatte ich eher vorgestellt, dass ich die Overlays selbst basteln muss und im Betriebssystem dann automatische Bankwechsel o.ä. vorsehe, so dass die Anwendungsentwicklung etwas einfacher wird. Also keine eigene Programmiersprache, das ist bissl heftig.

Eine Hardvard-Architektur erfordert zusätzliche Lade-/Schreibbefehle für den Codespeicher, um Code von externen Medien nachladen zu können. Außerdem müsste ich dann zwei Speichercontroller implementieren oder beide Speicher in einen gemeinsamen Speicher stecken (und verwalten). Die Befehle kann ich nicht länger gestalten als die Busbreite, da ich sonst zwei Zyklen zum Einlesen bräuchte.

Über Interrupts habe ich bisher noch nicht nachgedacht, möglicherweise realisiere ich das über ein Flag (und damit Polling); ein Befehl ist nicht vorgesehen. Software-Interrupts kann ich mit JMP auf Kosten eines Registers realisieren; Hardware-Interrupts möglicherweise auch.

Die Registerauswahl kostet mindestens zwei Gatterlaufzeiten, selbst wenn ich die Gatter bereits direkt implementiere, und in den Opcodes habe ich (bei 8 Bit) nur Platz für 8 Register...

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 20. March 2010, 20:01
Hallo,


zum selbstmodifizierenden code:
es war kein design ziel, sondern ist eine notwendigkeit aufgrund der einfachheit ;)
schon klar
Auf allen modernen CPUs ist das modifizieren von Code verboten, nur bei x86 macht man extra Anstrengungen bei der Verwaltung des Code-Cache um sowas zu ermöglichen obwohl kein modernes OS das benötigt oder auch nur zulässt.

zu den assemblern:
kpuasm: nur 281 zeilen (könnten aber reduziert werden)
kpuIIasm: 564 zeilen in 3 dateien (könnten auch reduziert werden)
xasm: 842 zeilen (werden noch mehr werden)
Ah ja, bei mir sind es momentan 150 kByte (Zeilen weiß ich jetzt nicht, muss ich mir mal ein passendes Tool besorgen) in 17 Dateien, wird aber noch deutlich mehr werden. Für die Immediate-Parameter von Befehlen hab ich einen Literal-Parser der schon allein über 1200 Zeilen hat, der kann momentan mit Zahlen in Binär, Dezimal und Hexadezimal umgehen (in C-Syntax und VHDL-Syntax). Des weiteren parst er ganze Gleichungen mit Klammern und Operatoren (+ - * / % | & ^ ~) mit Vorrangregeln, Label sind da aber noch nicht richtig implementiert. Gleitkommazahlen fehlen auch noch. Aus den Gleichungen wird ein Baum generiert welcher dann zusammengerechnet wird, bis jetzt kommt immer eine eindeutige Zahl raus weil ja noch die Labels fehlen. Dieser Baum wird dann ins Befehlsobjekt abgelegt und beim assemblieren mit den Label-Adressen ausgerechnet.

zu den befehlen:
ich mach das über eine böse variante, über string split und dann mit regex vergleichen, da ich da noch nichts vom ordentlichen compilerbau/assemblerbau gehört hab.
Naja, jeder fängt mal klein an. In meinem Assembler sind auch einige Stellen die ich noch mal ordentlich machen möchte.

je komplexer die cpu bzw deren befehlssatz, desto komplexer der assembler.
Da schreibst Du wahr!


Mit dem jetzigen Ziel habe ich eine recht hohe Ausführungsgeschwindigkeit, brauche aber viele Befehle für gewisse Aktionen.
Das ist ein generelles CPU-Design-Problem, entweder kleine und schnelle Befehle von dehnen man aber viele für einfache Aufgaben braucht oder mächtige Befehle die aber langsamer sind und auch mehr OpCode-Platz brauchen.

Angenommen, ich muss ständig die Bänke wechseln, das kostet jeweils 5-10 Takte zusätzlich.
Ja ich verstehe. Mehr Bänke gleichzeitig kosten Dich mehr Logik. Ich will nicht mit Dir tauschen. :roll:

Mit dem C-Ableger hatte ich eher vorgestellt, dass ich die Overlays selbst basteln muss und im Betriebssystem dann automatische Bankwechsel o.ä. vorsehe, so dass die Anwendungsentwicklung etwas einfacher wird.
Ich verstehe immer noch nicht was Du meinst.

Eine Hardvard-Architektur erfordert zusätzliche Lade-/Schreibbefehle für den Codespeicher
Und dafür fehlen Dir wertvolle Bits in den Befehlen.

Über Interrupts habe ich bisher noch nicht nachgedacht, möglicherweise realisiere ich das über ein Flag (und damit Polling); ein Befehl ist nicht vorgesehen. Software-Interrupts kann ich mit JMP auf Kosten eines Registers realisieren; Hardware-Interrupts möglicherweise auch.
Polling ist aber was anderes als Interrupts. Naja, Du wirst wissen was Du tust.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 20. March 2010, 21:40
Mit dem C-Ableger hatte ich eher vorgestellt, dass ich die Overlays selbst basteln muss und im Betriebssystem dann automatische Bankwechsel o.ä. vorsehe, so dass die Anwendungsentwicklung etwas einfacher wird.
Ich verstehe immer noch nicht was Du meinst.
Das Problem mit dem Banking bleibt ja erhalten, so oder so; wenn ich aber in der C-Library (bzw. im Kernel) entsprechende Unterstützung aber vorsehe, dann wird es vielleicht etwas weniger schmerzhaft. Die Programme würden dann nur noch eine Aufteilung in Overlays bekommen, sich aber nicht darum kümmern müssen, an welcher Adresse und in welcher RAM-Bank die benötigten Daten nun gerade liegen.

Schmerzhaft wird es ohnehin, da hab ich wenig Illusionen.

Eine Hardvard-Architektur erfordert zusätzliche Lade-/Schreibbefehle für den Codespeicher
Und dafür fehlen Dir wertvolle Bits in den Befehlen.
Eben.

Über Interrupts habe ich bisher noch nicht nachgedacht, möglicherweise realisiere ich das über ein Flag (und damit Polling); ein Befehl ist nicht vorgesehen. Software-Interrupts kann ich mit JMP auf Kosten eines Registers realisieren; Hardware-Interrupts möglicherweise auch.
Polling ist aber was anderes als Interrupts. Naja, Du wirst wissen was Du tust.
Das war eher entweder-oder gemeint. Möglicherweise realisiere ich keine Hardwareinterrupts (d.h. Pollingbetrieb), möglicherweise lässt sich eine simple "sichere PC nach Register, JMP an fixe Adresse" einfach realisieren, dann tue ich das natürlich. Inwieweit meine CPU überhaupt Hardwarestacks unterstützt, weiß ich bisher noch nicht; nested function calls und Syscalls sind dann halt wieder softwareseitig (z.B. Assemblermakro) realisiert.

Genau das ist aber der Grund, weswegen es mich interessiert, wie andere an solche Projekte herangehen - denn an Interruptverarbeitung habe ich wirklich noch nicht gedacht.

Wenn man die CPU hinreichend blöde macht, muss man halt alles in Software nachimplementieren. Im Extremfall kommt dann halt 'ne Turingmaschine bei raus, aber bissl mehr möchte ich schon können... außerdem programmiert sich eine Turingmaschine wirklich unangenehm. :-D

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: chris12 am 20. March 2010, 21:46
also wenn ich schätzen sollte hast du so ca 4000 zeilen in deinem assembler ...
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 20. March 2010, 22:51
Hallo,


Schmerzhaft wird es ohnehin, da hab ich wenig Illusionen.
Dann is ja gut wenn Du weißt auf was Du Dich da einlässt. :wink:

Das war eher entweder-oder gemeint. Möglicherweise realisiere ich keine Hardwareinterrupts (d.h. Pollingbetrieb), möglicherweise lässt sich eine simple "sichere PC nach Register, JMP an fixe Adresse" einfach realisieren, dann tue ich das natürlich. Inwieweit meine CPU überhaupt Hardwarestacks unterstützt, weiß ich bisher noch nicht;
Da gibt es IMHO 2 Wege: einmal werden für den IRQ-Mode (teilweise) andere Register benutzt so das Du nichts sichern musst aber noch mehr Register brauchst (ARM z.B. geht diesen Weg) oder Du hast einen HW-Stack und die CPU kann "sichere PC auf Stack und springe nach Irgendwohin" (x86 ist da ein klassisches Beispiel). Ich gehe den ARM-Weg aber in etwas anderer Form. ARM hat für jeden System-Mode (IRQ, Exception, SYSCALL ...) eigene Schatten-Register so das man diese Modi (theoretisch) beliebig verschachteln kann, soll heißen das man im SYSCALL-Handler noch ne Page-Not-Present-Exception verarbeiten kann und im Page-Not-Present-Handler könnte noch ne Illegal-Opcode-Exception kommen ohne das da was kollidiert (in echt begrenzt ARM das natürlich auf sinnvolle Möglichkeiten), das kostet aber viele Register (ARM hat über 30 HW-Register obwohl die SW immer nur 16 sehen kann). Ich möchte nur einen Register-Schatten (und auch nur für R56 bis R63) haben was aber bedeutet das im SYSCALL-Handler keine Exception auftreten darf und auch keine IRQs angenommen werden können. Aus einem System-Mode muss die CPU immer erst in den User-Mode zurück um dann wieder einen anderen System-Mode betreten zu können. Ist zwar ne Einschränkung aber ich denke damit kann ich gut leben und es spart reichlich Register. 64 normale Register sind schon ne Menge Zeugs, das kostet auch einiges an Logik-Ebenen bzw. Fan-Out und reduziert im Endeffekt den maximalen Takt für meine CPU.

Genau das ist aber der Grund, weswegen es mich interessiert, wie andere an solche Projekte herangehen - denn an Interruptverarbeitung habe ich wirklich noch nicht gedacht.
Ich vermute mal dieser Thread wird noch ne Weile benutzt werden. :-D

Wenn man die CPU hinreichend blöde macht, muss man halt alles in Software nachimplementieren.
Es kommt eben auf das richtige Verhältnis an. Was wirklich das Optimum ist merkt man aber erst wenn die ersten großen Programme laufen, bzw. dann stellt man eventuell fest das man nicht das Optimum getroffen hat.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 21. March 2010, 12:56
Hallo,


.... Anschließend werden die Code-Segmente assembliert. Dazu gibt es eine Vorrunde in der bei allen Befehlen geprüft wird wie groß sie mindestens werden damit jeder Befehl ein Initial-Offset bekommen kann. Dann soll der gesamte Code so lange immer wieder assembliert und optimiert werden bis sich das best-mögliche Ergebnis eingestellt hat (das Abbruchkriterium wird mir sicher einiges an Kopfzerbrechen bereiten).
Da steckt ein Fehler drin. Wenn man die Befehle versucht zu schrumpfen könnte es sein das nicht das kleinst mögliche Ergebnis dabei raus kommt.

Label1:
    b Label2
    viel_Code;
    b Label1
Label2:
Hierbei könnte es passieren das beide Sprünge gerade so nicht in die nächst kleinere Größer überführt werden können weil sie sich quasi gegenseitig blockieren, also die Entfernung auf Grund des "viel Code" gerade so zu groß ist für die nächst kleinere Größe aber wenn die Sprünge kleiner wären dann würde es eben gehen.
Das bedeutet das ich die Befehle in der Vorrunde erst mal auf die kleinste Möglichkeit setzen muss, falls das Label noch nicht auflösbar ist, und dann solange iterativ vergrößert wird bis die Größe aller Befehle gerade so ausreicht.


Danke an XanClic für diesen Hinweis, im IRC.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 22. March 2010, 12:54
Hallo,


(plus einen MAC-Befehl, den halte ich für nützlich)
Dir ist klar das ein MAC-Befehl ein 3 Operanden-Befehl ist?
In der Form "rA = rA + (r2 * r3)" must Du mindestens 3 Operanden angeben. Du kannst den Accu gerne auf 1 Bit reduzieren oder auch ganz weg lassen aber r2 und r3 müssen IMHO schon sein.


Mal ne andere Frage:
Wie groß würdet ihr die Pages in einem 64 Bit-System mindestens machen?
Ich würde gerne entweder 512 kBytes mit einem 3-stufigem Paging oder 64 kByte mit einem 4-stufigem Paging machen (Page-Descriptoren sind immer 16 Bytes, es ist dann auch jede beliebige größere Zweier-Potenz als Page-Größe möglich). 512 kBytes sind IMHO schon reichlich groß und unterlaufen etwas das Lokalitäts-Prinzip, dafür wird der TLB mit steigender Page-Größe immer effektiver. Bei 64 kByte wird dafür das Page-Directory etwas Speicherhungriger und jeder zusätzliche Level kostet wieder etwas Performance. Beim swappen auf HDD dürfte es kaum einen großen Unterschied geben, die mittlere Zugriffszeit ist deutlich länger als es dauert ein paar MByte zu lesen/schreiben.
Oder würdet ihr die Page-Table-Walker-Hardware ganz weg lassen und das der SW überantworten (viele CPUs machen das so)?
Oder eine Mischform? Beim Itanium z.B. gibt es eine Art großen TLB-L2-Cache im Speicher der von der HW durchsucht werden kann aber von der SW befüllt/verwaltet wird, der hat aber wegen dem Hash-Algorithmus ein paar Probleme mit variablen Page-Größen.
Wäre interessant was andere so dazu denken.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 22. March 2010, 20:22
Dir ist klar das ein MAC-Befehl ein 3 Operanden-Befehl ist?
In der Form "rA = rA + (r2 * r3)" must Du mindestens 3 Operanden angeben. Du kannst den Accu gerne auf 1 Bit reduzieren oder auch ganz weg lassen aber r2 und r3 müssen IMHO schon sein.
Ne, war mir nicht klar. :-(
So rückblickend stellt sich da auch die Frage der Ergebnisvermittlung, da ich mit dem jetzigen Ansatz nur ein Ergebnisregister beschreiben kann, d.h. eine Multiplikation zweier 8-Bit-Register nicht zurücksichern kann. Bäh. Pfui. Wie kannst du nur sowas sagen? :-D

Damit fällt ein Hardwaremultiplizierer eh flach. Allerdings hätte ich bevorzugt die 4-Operandenfassung des Befehls genommen (A = B + C * D), da ich vermute, dass diese nützlicher ist. Aber die Hälfte der Register, die ja auch vorher geladen werden müssen (=6 Takte für 3 Immediates plus Multiplikation) macht das ganze etwas schwieriger.

Mal ne andere Frage:
Wie groß würdet ihr die Pages in einem 64 Bit-System mindestens machen?
Ich würde das bevorzugt von der Größe der zu erwartenden Codes und Daten abhängig machen. Wenn deine Anwendungen viele und große Daten verarbeiten (Puffer), dann würde ich die Pagegröße schon größer ansetzen als bei Durchlaufverarbeitung (z.B. ständige FFT). Vielleicht so nen Faktor 1,5 auf die Durchschnitts-Puffergröße abgeschätzt auf die nächste Zweierpotenz? :-D

Für ein Allzwecksystem würde ich die Pagegröße klein machen, da du sonst recht viel Speicher verschwendest; das hängt dann aber auch wieder von der Verwaltung im Betriebssystem ab, inwieweit Anwendungen ihre Pages miteinander teilen können.

Abgesehen davon habe ich mich mit Paging nicht weiter beschäftigt und bitte daher um Entschuldigung, falls der Post etwas arg ahnungslos wirkt.

edit: Wenn du natürlich das Paging nur als Auslagerungsmöglichkeit für deine Segmente nutzt, kannst du als Richtmaß einerseits die Segmentgrößen nehmen, andererseits aber die Pages einfach sehr groß machen, in der Annahme, dass Paging nur bei Speicherknappheit (und damit wahrscheinlich sehr selten) aktiv wird.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 22. March 2010, 23:00
Hallo,


So rückblickend stellt sich da auch die Frage der Ergebnisvermittlung, da ich mit dem jetzigen Ansatz nur ein Ergebnisregister beschreiben kann, d.h. eine Multiplikation zweier 8-Bit-Register nicht zurücksichern kann.
Du könntest für das High-Byte vom Ergebnis auch ein extra Spezial-Register vorsehen (die 8 Bit PICs machen das so), aber um dieses Register lesen und schreiben (ohne schreiben kannst Du in einem IRQ-Handler nicht multiplizieren) zu können wirst Du wieder zusätzliche Befehle brauchen die wiederum Bits benötigen. Ein ekliges Dilemma.
Wenn Dein Ergebnis volle Größe hat solltest Du übrigens zwischen Signed und Unsigned unterscheiden können, was noch mal ein Bit im Befehl kostet. Genau das war der Grund warum ich neben den 20 Bit und 39 Bit Befehlen noch eine Variante mit 60 Bit haben wollte, damit mir nicht für interessante Befehle die Bits ausgehen.

Bäh. Pfui. Wie kannst du nur sowas sagen? :-D
Ich bin eben ein fieser Mensch. :-D

Allerdings hätte ich bevorzugt die 4-Operandenfassung des Befehls genommen (A = B + C * D), da ich vermute, dass diese nützlicher ist.
Die 4 Operandenvariante hat IMHO kaum Vorteile, der MAC-Befehl wird vorwiegend für DSP-Algorithmen und ähnliches Zeugs benutzt und da werden die Ergebnisse vieler Multiplikationen aufakkumuliert. Für sowas hab ich auch extra "R1:R2 = R1:R2 + ( R3 * R4 )", es gibt auch eine Version mit - anstatt +. 6 Operanden (für "R1:R2 = R3:R4 +/- ( R5 * R6 )") hätte ich aber nicht untergebracht, 4 unabhängige Operanden hab ich nur wenn der Akku nicht aus 2 Registern besteht "R1 = R2 +/- ( R3 * R4 )".


Ich würde das bevorzugt von der Größe der zu erwartenden Codes und Daten abhängig machen.
Das kann ich aber nicht vorhersagen. Es soll ja ein Allzweck-System werden.

Für ein Allzwecksystem würde ich die Pagegröße klein machen, da du sonst recht viel Speicher verschwendest; das hängt dann aber auch wieder von der Verwaltung im Betriebssystem ab, inwieweit Anwendungen ihre Pages miteinander teilen können.
Das mit der Speicherverschwendung (gerade bei kleinen Programmen) ist natürlich ein Problem (erinnert irgendwie an die Cluster-Größe bei FAT). Da die Basis-Adresse der Segmente wohl auf 64 kByte aligned ist (vielleicht auch weniger, da muss ich mal genau überlegen wie viele Bits ich in den Segment-Descriptoren wirklich für Flags brauche) können sich natürlich mehrere Segmente in einer größeren Page befinden.

Wenn du natürlich das Paging nur als Auslagerungsmöglichkeit für deine Segmente nutzt, kannst du als Richtmaß einerseits die Segmentgrößen nehmen, andererseits aber die Pages einfach sehr groß machen, in der Annahme, dass Paging nur bei Speicherknappheit (und damit wahrscheinlich sehr selten) aktiv wird.
Paging möchte ich vor allem für die Speicherdefragmentierung und andere Tricks nutzen aber natürlich auch zum swappen, falls ich mit meinem Projekt so weit komme das sich das lohnt.
Wichtig ist mir vor allem dass das Paging möglichst wenig Performance kostet und sich mit vertretbarem Aufwand im FPGA umsetzen lässt.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 22. March 2010, 23:57
Ich hatte als Gedanken einen Instruktionsbus, wo auf der steigenden Taktflanke die Daten eingelesen werden und auf der fallenden Flanke das Ergebnis ausgegeben wird. Da die meisten Befehle implizit mit dem Akkumulator arbeiten (der also zusätzlich eigene Leitungen in die ALU hat, aber damit nur lesbar ist), hätte ich für das Ergebnis somit nur 8 Bit (plus die Steuerleitungen zur Registerauswahl).

Damit lässt sich keine direkte Multiplikation realisieren; alternativ könnte ich natürlich einfach zwei Befehle haben (einen für den High-, einen für den Low-Teil), aber das kostet mich einen weiteren Opcode, den ich nicht habe. Es wird wohl doch auf eine simple LEFT SHIFT-Operation hinauslaufen.

Den Unterschied zwischen signed und unsigned wird die CPU nicht kennen, das ist dann Software. Und wenn ich das Einserkomplement nehme, dann ist das eben so, auch wenn das die Unterscheidung zwischen "-0" und "+0" erlaubt. :-)

Wenn dieses Monster fertig ist, werde ich vielleicht möglicherweise über eine sinnvollere CPU nachdenken, aber Bits sind halt teuer und richtig aufwändig, daher spare ich an jeder Stelle.

Mehrere Segmente innerhalb einer Page halt ich für nicht besonders zweckmäßig, eher dann eine Pagegröße, die mit den Segmentbasisadressen korreliert. Eine andere Idee wäre es, nicht den gesamten Adressraum in Pages aufzuteilen, sondern nur einen kleinen Teil. Da das Paging nur für Tricksereien verwendet wird, braucht es ja nicht unbedingt vollständig aktiv zu sein. Defragmentieren wäre dann natürlich schwieriger/langsamer, aber wie oft muss das geschehen? Wenn du die Programme etwas daraufhin anpasst [vgl. NOMMU-uClinux, welches aber nicht defragmentiert], sollte das ohnehin nicht nötig sein. Du sparst dann viele Bits und vermutlich einige Komplexität in der MMU ein.

Swappen kannst du, grundsätzlich genügend RAM vorausgesetzt, auch mit ganzen Segmenten. Das hat natürlich eine gröbere Granularität, wenn du das gesamte Codesegment auslagern musst, weil die Anwendung grad nicht aktiv ist. Bei mehreren aktiven Tasks sollte das aber nicht weiter stören.

Ich vermute mal, dass die 64 Bit-Variante aus praktischem Ansatz (Befehlslänge usw.) entsteht und nicht aus der Bedingung, da mehr als 4 GB RAM einzubauen. Wenn deine MMU also immer nur 4 GB abdecken kann, wäre das also kein Problem. Und wenn du mehr RAM hast, musst du halt die 4 GB-Blöcke getrennt defragmentieren.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 23. March 2010, 09:08
Hallo,


Ich hatte als Gedanken einen Instruktionsbus, wo auf der steigenden Taktflanke die Daten eingelesen werden und auf der fallenden Flanke das Ergebnis ausgegeben wird. Da die meisten Befehle implizit mit dem Akkumulator arbeiten (der also zusätzlich eigene Leitungen in die ALU hat, aber damit nur lesbar ist), hätte ich für das Ergebnis somit nur 8 Bit (plus die Steuerleitungen zur Registerauswahl).
Der Akku sollte aber auch immer beschrieben werden können, er sollte also auch für das Ergebnis eine eigene Verbindung zur ALU haben. Außerdem hattest Du doch mal 2 Akkus im Plan, da könnte ja jeder Akku eigene Verbindungen zur ALU haben, damit währe "A1:A0 = A1:A0 + ( R2 * R3 )" möglich und Du müsstest nur 2 normale Register im Befehl codieren. Ich weiß das kostet Lötarbeit, aber für umsonst gibt es eben auch keine TTL-CPU.

Den Unterschied zwischen signed und unsigned wird die CPU nicht kennen, das ist dann Software.
Diese Unterscheidung in SW zu machen hallte ich für ungeschickt, um aus einem Unsigned errechnetem Ergebnis von 2 Signed Faktoren das richtige Ergebnis zu machen braucht der Compiler viele Befehle da kann er auch gleich händisch multiplizieren.

Und wenn ich das Einserkomplement nehme, dann ist das eben so, auch wenn das die Unterscheidung zwischen "-0" und "+0" erlaubt. :-)
Was, Du willst ernsthaft über Einerkomplement nachdenken?!? :-o Dann denke vielleicht doch über eine eigene Programmiersprache nach. Ich weiß das C da keine Vorgaben macht aber viele existierende Programme gehen einfach davon aus. Ich hatte es auch noch nie mit einer CPU zu tun die nicht mit dem Zweierkomplement arbeitet, mit so einer Idee wirst Du noch mehr inkompatibel zur restlichen Welt.

Wenn dieses Monster fertig ist, werde ich vielleicht möglicherweise über eine sinnvollere CPU nachdenken, aber Bits sind halt teuer und richtig aufwändig, daher spare ich an jeder Stelle.
Vielleicht solltest Du für Deine CPU erst mal einen Simulator programmieren damit Du ein Gefühl für den geplanten Befehlssatz bekommst. Wenn Du dann mit dem Ergebnis zufrieden bist kannst Du immer noch überlegen wie sich das per TTL realisieren lässt. Ein Simulator sollte für Deine CPU ja nun nicht so schwer sein, bestimmt gibt Dir chris12 die nötigen Tipps. :wink:


Mehrere Segmente innerhalb einer Page halt ich für nicht besonders zweckmäßig
Warum? Pages dienen doch bei mir nicht direkt der Speicherverwaltung. Und da ich jede beliebige Zweier-Potenz (>= kleinste Page-Size) erlauben will, um die TLB-Effizienz so hoch wie möglich zu halten, wird es sowieso der Regelfall sein das sich in einer Page mehrere Segmente befinden. Zum swappen werden die Pages natürlich auf die kleinste Größe reduziert.

Eine andere Idee wäre es, nicht den gesamten Adressraum in Pages aufzuteilen, sondern nur einen kleinen Teil.
Das habe ich eh vor. Der Speicher in dem z.B. die Peripherie liegt benötigt kein Paging.

Wenn du die Programme etwas daraufhin anpasst [vgl. NOMMU-uClinux, welches aber nicht defragmentiert], sollte das ohnehin nicht nötig sein.
Die Programme extra dafür anpassen möchte ich eigentlich nicht. Den Speicher von Flat-Memory-Programmen kann man ja auch gar nicht defragmentieren (wegen Pointern usw.), also kann uCLinux das nicht tun.

Swappen kannst du, grundsätzlich genügend RAM vorausgesetzt, auch mit ganzen Segmenten. Das hat natürlich eine gröbere Granularität, wenn du das gesamte Codesegment auslagern musst, weil die Anwendung grad nicht aktiv ist. Bei mehreren aktiven Tasks sollte das aber nicht weiter stören.
Meinst Du das ernst?? Komplette Code/Daten-Segmente auszulagern ist schon wirklich sehr strange. Für gewöhnlich ist es so das von allen Programmen immer etwas aktiv ist, und wenn es nur ein Signal-Handler ist der ab und an mal Nachrichten vom OS abhandelt. Das heißt das von jedem Programm wohl ein paar Pages im RAM bleiben müssen. Der Trick des Swapping per Paging ist das von jeden Programm nur das absolute Minimum im RAM bleibt, nur auf 0 wird sich das Minimum nicht reduzieren lassen. Segmentweises Swapping halte ich persönlich für keine gute Idee, auch wenn Du nicht der erste bist der das vorschlägt.

Ich vermute mal, dass die 64 Bit-Variante aus praktischem Ansatz (Befehlslänge usw.) entsteht und nicht aus der Bedingung, da mehr als 4 GB RAM einzubauen.
Sicher nicht im ersten Anlauf, da werde ich wohl mit höchstens 1 GByte auskommen müssen :cry:, aber grundsätzlich möchte ich schon mehr als 4 GByte RAM ermöglichen.

Wenn deine MMU also immer nur 4 GB abdecken kann, wäre das also kein Problem. Und wenn du mehr RAM hast, musst du halt die 4 GB-Blöcke getrennt defragmentieren.
Das defragmentieren hatte ich mir eher anders vorgestellt. Wenn es nötig ist dann wird ein Paging-Directory erstellt das den physischen RAM 1:1 mappt und zusätzlich nochmal in einem virtuellem Bereich hinter dem RAM, dann wird das Paging eingeschaltet und alle Segement-Basis-Adressen in den virtuellen Bereich verlegt, danach werden die Pages im physischen Speicher umsortiert und das Paging für den virtuellen Bereich immer mit angepasst, als letztes wird der 1:1 Bereich wieder auf 1:1 gebracht, die Segment-Basis-Adressen wieder in den 1:1 Bereich verlegt und das Paging abgeschaltet. Das Problem ist eher das dieser Vorgang Zeit kostet und in dieser Zeit das OS natürlich trotzdem Speicherveraltungsaufgaben (Segmente anlegen/löschen/vergrößern/verkleinern) wahrnehmen muss.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 26. March 2010, 23:45
Hallo,


Und wenn du mehr RAM hast, musst du halt die 4 GB-Blöcke getrennt defragmentieren.
Das wird nicht klappen wenn Segmente existieren die größer als 4 GB sind, was ja auf einer 64 Bit-Architektur möglich sein soll.


Das defragmentieren hatte ich mir eher anders vorgestellt.....
Ich hab da noch mal drüber nachgedacht.
Das Paging an sich bleibt besser immer angeschaltet und als benutzter Bereich wird Platz oberhalb des physischen RAM genommen.
Eine Memory-Map könnte etwa so aussehen (auf einer 32Bit-Plattform):
Das OS verwaltet den linearen Speicher für den physischen RAM ganz normal. Wenn dann ein Segment vergrößert werden soll das zwischen 2 anderen liegt und deshalb nicht einfach so wachsen kann dann wird nur der zusätzliche Speicher an einer anderen Stelle (oder mehreren Stellen) im RAM alloziert, anschließend wird ein passender Bereich für das vergrößerte Segment (in einem Stück) im virtuellem Speicher alloziert und ein Mapping ins Paging-Directoy eingetragen dass das gesamte Segment aus dem physischen RAM in den virtuellen Speicher spiegelt (in möglichst großen Pages), dann wird die Basis-Adresse und die Größe im Segmentdescriptor geändert (das muss anderen CPUs irgendwie signalisiert werden, am besten mit einer Broadcast-Message), der SYSCALL zum vergrößern des Segments ist nun beendet. Als nächstes wird der Memory-Management-Dämon (User-Mode-Prozess mit geringer Priorität) aktiv der einen neuen zusammenhängenden Bereich im physischen RAM für das Segment sucht und alloziert, dann wird Page-Weise kopiert (in möglichst kleinen Pages) und immer das Mapping für den virtuellen Speicher mit angepasst. Wenn das fertig ist werden die alten Speicher-Bereiche des Segments freigegeben, die Segment-Basis-Adresse aktualisiert (zeigt jetzt wieder auf physischen Speicher für den kein Paging benutzt wird) und als letztes wird das Mapping aus dem Paging-Directory gelöscht und der virtuelle Speicher freigegeben. Das Segment ist damit verschoben ohne dass das User-Space-Programm das gemerkt hat. Falls im physischen RAM keine geeignet große Lücke für das vergrößerte Segment existiert müssen andere Segmente eben erst zusammen geschoben werden, das geschieht auf die selbe Art und so ähnlich wie bei einem klassischen Festplatten-Defragmentierer. Das zusammen schieben kann auch prophylaktisch im Hintergrund geschehen damit im Ernstfall schneller gehandelt werden kann.

Swapping lässt sich in dieses Konzept auch integrieren indem Segmente von dehnen Teile ausgelagert wurden im virtuellen Speicher bleiben und deren Mapping eben nur teilweise auf physischen Speicher zeigt (der Rest ist ausgelagert). Wenn der Memory-Management-Dämon merkt das die Summe der Segmentgrößen kleiner als der physische Speicher ist dann werden alle Segmente im physischen Speicher zusammen geschoben und fehlende Pages von der Platte eingelesen um dann die Basis-Adressen wieder in den physischen Speicher zu verlegen damit das Paging nicht benutzt werden muss. Swapping ist aber definitiv recht weit hinten auf meiner Dringlichkeitsliste.

Ich denke das kann so funktionieren, die größte Schwierigkeit ist eher das sich die Situation immer wieder (u.a. mitten in den Verschiebe-Vorgängen) ändert und der Memory-Management-Dämon dann immer wieder einen neuen Plan entwickeln muss.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 27. March 2010, 18:14
Hallo,

ich hatte vor, den Akku direkt mit der ALU zu verbinden (und wenn es zwei gibt, schiebe ich ein Gatter dazwischen zur Selektion), so dass ich nur ein einziges Register lesen muss. Beide Akkus gemeinsam als Zielregister wird es eher nicht geben. Wenn ich keine Hardwaremultiplikation habe, dann brauche ich auch keine Entscheidung über signed/unsigned treffen; das oberste Bit der letzten Operation bleibt dann einfach ein Flag und gut ist.

Den Speicher von Flatmem-Programmen kann man wirklich nicht defragmentieren, aber man kann - angenommen, die Segmente sind hinreichend groß - die Segmente im physischen RAM verschieben. Dazu benötigt man kein Paging, die Programme dürfen dann aber keine Verweise über Segmentgrenzen erlauben. Das Swappen von ganzen Segmenten ist an sich gar nicht so abwegig, das meinte ich schon ernst. Einerseits vereinfacht es die Implementation, andererseits ist es eine Notlösung, die nie auftreten sollte. Das Umsortieren des Speichers (Verschieben der Segmente) erfordert ohnehin eine wie auch immer geartete Auslagerung.

Swapping effizient zu machen bringt meiner Meinung nach wenig, aber das hängt auch grundsätzlich vom Anwendungszweck ab. Mein System mit derzeit 2 GB RAM unter Linux fängt nur damit an, wenn irgendein Programm Amok läuft. Effektiv wird es nie genutzt.

Meine Meinung ist es, auf Paging ganz zu verzichten oder aber es vollständig zu implementieren und dann auch zu nutzen. Alles andere ist verschwendetes Silizium. Zumal mich die Defragmentierung irgendwie an einen Garbage Collector erinnert...  Zum richtigen Defragmentieren ist es eigentlich nicht nötig, wenn du die gesamte Segmentverwaltung dem Betriebssystem obliegt und Programme nur ihre eigenen Segmente und ein unverschiebbares Systemsegment verwenden können. Mit Paging könntest du das Defragmentieren durch ein Ummappen sehr schnell lösen und im Hintergrund später die reale Umsortierung machen, das halte ich aber (insbesondere bei mehreren CPUs) für ein sehr kompliziertes Unterfangen.

Gibt es vielleicht die Möglichkeit, nur sehr wenige Pages (vielleicht 16) zu erlauben, und damit die Größe der Pagetable stark zu begrenzen? Diese ließe sich eventuell als Gatter realisieren und der Pagetable-Lookup wäre dann kostenlos. Für Tricksereien ist das möglicherweise auch hinreichend.

Segmentierung finde ich prinzipiell toll, nur die damit einhergehenden Probleme (die es nur für knappes oder zersplittertes RAM gibt) finde ich schwierig zu lösen. Dynamische Segmentgrößen vereinfachen das etwas. In meiner Implementierung wäre das Defragmentieren allerdings atomisch, so dass der Kernel sich ungestört um die Speicherverwaltung kümmern kann. Damit gibst du dann allerdings die Echtzeitfähigkeit komplett auf - es sei denn, die Programme sind "lieb".

Naja, sind alles nur Gedankensplitter.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 28. March 2010, 00:16
Hallo,


ich hatte vor, den Akku direkt mit der ALU zu verbinden
So soll es ja auch sein, der Akku hat nen direkten Draht zur ALU damit jeder Befehl ihn immer benutzen kann.

Wenn ich keine Hardwaremultiplikation habe, dann brauche ich auch keine Entscheidung über signed/unsigned treffen; das oberste Bit der letzten Operation bleibt dann einfach ein Flag und gut ist.
Bei den Strichrechnungen im Zweierkomplement gibt es keinen Unterschied zwischen signed und unsigned, wie das im Einerkomplement ist weiß ich gar nicht. Da fällt mir ein das die ganzen eingebauten Operatoren in VHDL auch alle vom Zweierkomplement ausgehen, eine CPU mit Einerkomplement in VHDL zu entwickeln dürfte ziemlich frustrierend sein.

Ohne HW-Multiplikation muss der Compiler diese übernehmen, ich glaube der gcc hat dafür ne Reihe buildins die auf (zu) kleinen CPUs benutzt werden können.


aber man kann - angenommen, die Segmente sind hinreichend groß - die Segmente im physischen RAM verschieben.
Hä?? Was hat die Größe der Segmente damit zu tun?

Dazu benötigt man kein Paging,
Dann muss das verschieben atomar sein, das betreffende Programm muss während dieser Zeit schlafen. Bei großen Segmenten ist das schon ziemlich doof, das will ich den Programmen nicht zumuten. Bei SMP muss der OS-Kernel dann auch noch sicherstellen das wirklich keiner der Threads des betreffenden Programms auf anderen CPUs laufen, ist zwar machbar aber umständlich. Bei "beerbten" Segmenten (wegen shared Memory oder IPC) ist das noch schwieriger, weil man noch mehr Programme (alle ihre Threads) deaktivieren muss. Das atomare verschieben von Segmenten würde quasi das gesamte System lahmlegen.

die Programme dürfen dann aber keine Verweise über Segmentgrenzen erlauben.
Was genau meinst Du? Offsets für ein bestimmtes Segment dürfen nur vom dessen Minimum bis zu dessen Maximum gehen (beides ist bei mir im Segment-Descriptor eingetragen, bei x86 gibt es kein Minimum dort fangen Segmente immer mit Offset 0 an), alles andere löst beim Speicherzugriff eine Exception aus. So einen Mist wie die unflexieble Segmentierung im x86-Real-Mode will ich definitiv nicht machen.

Das Swappen von ganzen Segmenten ist an sich gar nicht so abwegig, das meinte ich schon ernst.
Das bedeutet aber dass das betreffende Programm komplett deaktiviert ist wenn eines seiner Segmente nicht im RAM ist. Wenn z.B. ein großes Office-Programm (falls sowas jemals auf meinem OS laufen sollte) für jeden Tastendruck wieder komplett in den Speicher muss obwohl der Key-Event-Handler nur den Text um einen Buchstaben erweitern müsste und nur das aktualisieren der Bildschirmdarstellung über die GUI antriggern müsste. Das stelle ich mir wahrlich nicht praktikabel vor. Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.

Das Umsortieren des Speichers (Verschieben der Segmente) erfordert ohnehin eine wie auch immer geartete Auslagerung.
Nicht unbedingt. Das ich die Segmente, während des defragmentierens, virtuell in einen anderen Bereich verlagern möchte dient eher der Performance (hat aber auch noch ein paar praktische Gründe), dann müssen die anderen Segmente, die direkt im physischen Speicher angesprochen werden, nicht das Paging benutzen. Man könnte die Segmente, während des defragmentierens, auch im normalen linearen Speicher lassen aber dann müsste man für diesen Bereich, in dem der physische Speicher eigentlich immer 1:1 gemappt ist, das Paging anschalten und das kostet etwas Performance bei allen Programmen. Wenn nur das eine Segment in einem speziellen Bereich liegt dann ist nur das eine Programm betroffen und das auch nur bei Zugriffen auf das eine Segment welches gerade verschoben/defragmentiert wird.
Der Performanceverlust durch das Paging kann durchaus hoch sein, das 4-stufige Paging von x86_64 im Long-Mode soll angeblich ca. 20% kosten. Deshalb versuchen AMD und Intel die TLBs möglichst groß und flexibel zu machen und die OSe versuchen möglichst große Pages zu nutzen um diesen negativen Effekt möglichst klein zu halten.

Swapping effizient zu machen bringt meiner Meinung nach wenig, aber das hängt auch grundsätzlich vom Anwendungszweck ab. Mein System mit derzeit 2 GB RAM unter Linux fängt nur damit an, wenn irgendein Programm Amok läuft. Effektiv wird es nie genutzt.
Das Swapping ist sicher nicht besonders performancerelevant aber eine zügige Arbeitsweise wird bestimmt nicht schaden.

Meine Meinung ist es, auf Paging ganz zu verzichten oder aber es vollständig zu implementieren und dann auch zu nutzen. Alles andere ist verschwendetes Silizium.
Da hast Du völlig recht. Deshalb möchte ich Paging ordentlich implementieren und es genau dafür nutzen wofür es einst ersonnen wurde "virtuelle Speichermehrung". Das dazu noch ein bisschen sonstige Speicher-Magie kommt ist bei Segmentierung natürlich mehr als nur willkommen.

Zum richtigen Defragmentieren ist es eigentlich nicht nötig, wenn du die gesamte Segmentverwaltung dem Betriebssystem obliegt und Programme nur ihre eigenen Segmente und ein unverschiebbares Systemsegment verwenden können.
Ich verstehe nicht was Du damit meinst. Natürlich dürfen Programme nur innerhalb ihrer eigenen Segmente arbeiten (der Kernel darf natürlich mehr). Die eigenen Segmente, welche alle in der zum Programm gehörenden LDT stehen, sind quasi der Kontext. Die Programme haben dabei keinen Einfluss darauf wo sich ihre Segmente im linearen Speicher befinden, das wissen die noch nicht mal (außer natürlich Geräte-Treiber die ihrem Gerät mitteilen müssen wo im physischen Speicher das Gerät Daten ablegen/holen soll, dazu muss ein Segment aber gelockt werden und ist damit nicht mehr verschiebbar, sieh Dir dazu mal XMS (http://de.wikipedia.org/wiki/XMS) an).

Mit Paging könntest du das Defragmentieren durch ein Ummappen sehr schnell lösen und im Hintergrund später die reale Umsortierung machen, das halte ich aber (insbesondere bei mehreren CPUs) für ein sehr kompliziertes Unterfangen.
Ja, genau darum geht es mir. Das ich mir da keine leichte Übung vorgenommen hab ist mir bewusst. Warum sollte das mit mehreren CPUs schwerer werden?

Gibt es vielleicht die Möglichkeit, nur sehr wenige Pages (vielleicht 16) zu erlauben ....
Das ist viel zu grob. Ein Segment muss bei mir immer mindestens eine minimale Page (also das was das Paging als kleinste Page unterstützt, 4 kByte auf der 32 Bit-Version meiner Plattform, das ist dann auch das minimale Alingment der Segment-Basis-Adresse) ausfüllen, diese minimale Page können sich nicht mehrere Segmente teilen (sonst wäre das defragmentieren nicht möglich). Erst wenn größere Pages benutzt werden können in einer größeren Page auch mehrere Segmente drin liegen. Die Segmentierung weiß davon ja nichts und die größeren Pages dienen nicht der Verwaltung sondern nur der Performance (so wie auf allen anderen Plattformen auch).

Segmentierung finde ich prinzipiell toll, nur die damit einhergehenden Probleme (die es nur für knappes oder zersplittertes RAM gibt) finde ich schwierig zu lösen.
Segmentierung, gefällt mir schon lange. Aber ich finde die ist selbst im 386-Protected-Mode nur sehr halbherzig umgesetzt. Theoretisch ließe sich meine OS-Idee auch dort umsetzen aber da gibt es keinen Pfad zu 64 Bit (im Long-Mode gibt es keine Segmentierung mehr), außerdem finde ich die PC-Plattform (mitsamt der CPU-Architektur) einfach nur echt Scheiße.

Dynamische Segmentgrößen vereinfachen das etwas.
Dynamische Segmente sind für mein Konzept die Kern-Idee, ohne dem geht nichts davon.

In meiner Implementierung wäre das Defragmentieren allerdings atomisch, so dass der Kernel sich ungestört um die Speicherverwaltung kümmern kann. Damit gibst du dann allerdings die Echtzeitfähigkeit komplett auf - es sei denn, die Programme sind "lieb".
Atomische Defragmentierung will ich nicht. Programme die freiwillig auf bestimmte Segmente mal nicht zugreifen stelle ich mir auch sehr schwierig vor, da müsste man vor jedem Speicherzugriff erst den Segment-Selector prüfen ob der auf der roten Liste, mit den momentan verbotenen Segmenten, steht.


Naja, sind alles nur Gedankensplitter.
Dafür ist dieser Thread doch da.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 28. March 2010, 16:39
Hallo,

aber man kann - angenommen, die Segmente sind hinreichend groß - die Segmente im physischen RAM verschieben.
Hä?? Was hat die Größe der Segmente damit zu tun?
Wenn der RAM voll ist, kannst du ganze Segmente nicht mehr ohne Auslagerung verschieben, ausgegangen von "kein Paging".

Dazu benötigt man kein Paging,
Dann muss das verschieben atomar sein, das betreffende Programm muss während dieser Zeit schlafen. Bei großen Segmenten ist das schon ziemlich doof, das will ich den Programmen nicht zumuten.
Also mein Core2Duo hat unter Linux eine Speicherperformance von ~2 GB/s. Um also ein paar MB umherzuschieben, gehen dir ein paar Mikrosekunden (maximal ms) verloren.

Schlafen legen müsstest du nur die beiden Programme, deren Segmente gerade umhergeschoben werden.

Bei SMP muss der OS-Kernel dann auch noch sicherstellen das wirklich keiner der Threads des betreffenden Programms auf anderen CPUs laufen, ist zwar machbar aber umständlich. Bei "beerbten" Segmenten (wegen shared Memory oder IPC) ist das noch schwieriger, weil man noch mehr Programme (alle ihre Threads) deaktivieren muss. Das atomare verschieben von Segmenten würde quasi das gesamte System lahmlegen.

Das System sollte an sich schon nicht stehenbleiben, nur hängt bei mir im Hinterkopf halt immernoch der Gedanke, wann denn die Defragmentierung nötig ist. Das ist sie kurz vorm Swappen (oder bei hinreichend langer Uptime mit vielen sich schlecht verhaltenden(*) Programmen). Also im Zweifelsfall fast nie.

Wenn meine Festplatte beschäftigt ist (oder der Auslagerungsmarathon beginnt), dann steht mein Linux auch komplett. Für ein Allzwecksystem ist das relativ normal, und alles unter ner gelegentlichen Viertelsekunde merkt man nicht.

(*): Schlecht verhalten tun sich Programme, welche einen großen Puffer alloziieren, diesen nutzen und etwas ausrechnen, für das Ergebnis aber keinen weiteren Puffer alloziieren, sondern nur den nicht mehr genutzten Teil freigeben. Sehr schlimm wird das, wenn im Puffer dann der letzte Teil behalten und der große erste Teil freigegeben wird. Das ist den uClibc-Leuten aufgefallen und stellt für diese ein großes Problem dar.

die Programme dürfen dann aber keine Verweise über Segmentgrenzen erlauben.
Was genau meinst Du? Offsets für ein bestimmtes Segment dürfen nur vom dessen Minimum bis zu dessen Maximum gehen (beides ist bei mir im Segment-Descriptor eingetragen, bei x86 gibt es kein Minimum dort fangen Segmente immer mit Offset 0 an), alles andere löst beim Speicherzugriff eine Exception aus. So einen Mist wie die unflexieble Segmentierung im x86-Real-Mode will ich definitiv nicht machen.

Achso, naja ich gehe immer davon aus, dass ein Segment bei 0 anfängt (ist für mich einer der Hauptvorteile) und eine Segmentnummer dazukommt. Wenn du den Zugriff auf programmfremde Segmente hardwareseitig unterbindest (Speicherschutz), ist das kein Problem mehr. Ich kenne halt nur die simple Mechanik des 8086 :-) Wenn dort ein Programm anfängt mit "ich will aber 0x5:0x00f0" (z.B. die IPC-Tabelle eines anderen Prozesses), kannst du Segment 5 derweil ja nicht verschieben. Mit Speicherschutz geht das eh nicht, also ist es egal.

Das Swappen von ganzen Segmenten ist an sich gar nicht so abwegig, das meinte ich schon ernst.
Das bedeutet aber dass das betreffende Programm komplett deaktiviert ist wenn eines seiner Segmente nicht im RAM ist. Wenn z.B. ein großes Office-Programm (falls sowas jemals auf meinem OS laufen sollte) für jeden Tastendruck wieder komplett in den Speicher muss obwohl der Key-Event-Handler nur den Text um einen Buchstaben erweitern müsste und nur das aktualisieren der Bildschirmdarstellung über die GUI antriggern müsste. Das stelle ich mir wahrlich nicht praktikabel vor.
Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff, ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle. Am besten, man sieht genug RAM vor, seit wenigen Jahren ist das ja endlich möglich. Swapping ist eine Notlösung und war (auch) einen Grund für Paging überhaupt. Der einzige Vorteil mit Paging ist die feinere Granlarität und damit die Möglichkeit, geringe Knappheit gut abzufangen.

Davon würde ich aber nicht mehr ausgehen; dann lieber genug physischen RAM (ggf. nachsteckbar) vorsehen.

Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.
Hängt vom Anwendungsfall ab.

Das Umsortieren des Speichers (Verschieben der Segmente) erfordert ohnehin eine wie auch immer geartete Auslagerung.
Nicht unbedingt. ...
Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon. Ich hätte dann halt auf ein Zwischenmedium ausgelagert, du lagerst in den virtuellen Adressraum aus. Das Umsortieren bei zu sehr zerstückeltem RAM sollte dagegen auch im physischen Bereich nicht zu lange dauern, da es dort meist um viele sehr kleine Segmente geht, für die noch Platz vorhanden sollte. Sonst ist der RAM voll und es tritt Fall 1 ein.

Wenn in der Zeit des Defragmentierens die Gesamtsystemleistung um 5-25% sinkt (du möchtest sicherlich kein 4stufiges Paging haben), stört das auch nicht weiter, wäre sogar eine starke Verbesserung gegenüber dem Halt der betreffenden Programme. Bei einer angenommenen Performance von 200 MIPS verlierst du dann vielleicht 20-30 MIPS, das merkt man nichtmal - solange man die Anwendung nicht auf die CPU zuschneidet (d.h. Echtzeitbetrieb).

Swapping effizient zu machen bringt meiner Meinung nach wenig, aber das hängt auch grundsätzlich vom Anwendungszweck ab. Mein System mit derzeit 2 GB RAM unter Linux fängt nur damit an, wenn irgendein Programm Amok läuft. Effektiv wird es nie genutzt.
Das Swapping ist sicher nicht besonders performancerelevant aber eine zügige Arbeitsweise wird bestimmt nicht schaden.
Ich würde versuchen vorzusorgen, dass dieser Fall nie eintritt. Er sollte ohnehin sehr selten sein.

Mit Paging könntest du das Defragmentieren durch ein Ummappen sehr schnell lösen und im Hintergrund später die reale Umsortierung machen, das halte ich aber (insbesondere bei mehreren CPUs) für ein sehr kompliziertes Unterfangen.
Ja, genau darum geht es mir. Das ich mir da keine leichte Übung vorgenommen hab ist mir bewusst. Warum sollte das mit mehreren CPUs schwerer werden?
Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).

Gibt es vielleicht die Möglichkeit, nur sehr wenige Pages (vielleicht 16) zu erlauben ....
Das ist viel zu grob. ...
Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst. Da die Tabelle dann extrem klein bleibt, kannst du sie in ein spezielles Register eintragen, wo die Lookups kostenlos sind und du keine Performance verlierst. Paging kostet ja nur deshalb so viel, weil die Pagingtabelle im RAM liegt und du dadurch Unmengen an zusätzlichen RAM-Zugriffen hast, die vom Cache nur ungenügend abgefedert werden können. Oder seh ich da grundsätzlich was falsch?

In meiner Implementierung wäre das Defragmentieren allerdings atomisch, so dass der Kernel sich ungestört um die Speicherverwaltung kümmern kann. Damit gibst du dann allerdings die Echtzeitfähigkeit komplett auf - es sei denn, die Programme sind "lieb".
Atomische Defragmentierung will ich nicht. Programme die freiwillig auf bestimmte Segmente mal nicht zugreifen stelle ich mir auch sehr schwierig vor, da müsste man vor jedem Speicherzugriff erst den Segment-Selector prüfen ob der auf der roten Liste, mit den momentan verbotenen Segmenten, steht.
Naja, wenn du keinen Speicherschutz hast, dann kann sich jedes Programm eine Segmentnummer zusammenbauen und dort zugreifen. Wenn du das nicht möchtest, dann musst du bei jedem Segmentzugriff nachschauen, ob das betreffende Segment in der weißen Liste des Prozesses steht...

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 29. March 2010, 17:27
Hallo,


Wenn der RAM voll ist, kannst du ganze Segmente nicht mehr ohne Auslagerung verschieben, ausgegangen von "kein Paging".
Ach so, Du bist von mit ohne Paging ausgegangen. Paging ist für mich integraler Bestandteil meines Konzepts, und damit implizit immer mit dabei. :-D

Also mein Core2Duo hat unter Linux eine Speicherperformance von ~2 GB/s.
Meine FPGA-Lösung kommt hoffentlich auf mehr als ein Zwanzigstel davon (also etwa gut 100 MBytes/s Kopier-Transferrate), und dafür werde ich mich schon anstrengen müssen. Damit möchte ich sicher nicht Segmente mit eventuell mehreren GBytes atomar verschieben müssen. Vielleicht wird es mehr wenn der Kopiervorgang von der CPU gemacht wird an welcher der Speicher dran hängt, damit würde man wenigstens die Latenzzeiten der Kommunikation zwischen den CPUs umgehen und die DRAM-Controller im Spartan 6 bringen bestimmt auch deutlich mehr als 200 MBytes/s.

Um also ein paar MB umherzuschieben
Die Segmente können schon richtig groß werden, es hindert doch niemand die Programme daran mal ordentlich Speicher zu allozieren. Zumindest dürfen speicherhungrige Programme kein KO-Kriterium für mein Konzept sein.

Das System sollte an sich schon nicht stehenbleiben,
Aber die Auswahl an lauffähigen Prozessen könnte schon erheblich eingeschränkt sein. Außerdem dürften vor allem Programme die gerade im User-Focus stehen (und daher auf User-Aktionen reagieren müssen) am ehesten ihre Segmente vergrößern wollen und da fallen dem User Totzeiten natürlich als erstes auf.

nur hängt bei mir im Hinterkopf halt immernoch der Gedanke, wann denn die Defragmentierung nötig ist. Das ist sie kurz vorm Swappen (oder bei hinreichend langer Uptime mit vielen sich schlecht verhaltenden(*) Programmen). Also im Zweifelsfall fast nie.
Naja, das ist wie auf einer Festplatte, nach langer Benutzung wird die Belegung des physischen Speichers schon ziemlich zerklüftet sein. Ich denke es wird ziemlich schnell passieren das Defragmentierung nötig wird, mit steigender Speicherauslastung natürlich noch schneller. Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.

Wenn meine Festplatte beschäftigt ist (oder der Auslagerungsmarathon beginnt), dann steht mein Linux auch komplett.
Also mein Linux steht da nicht komplett, das Linux versucht doch als erstes Pages auszulagern die schon eine Weile nicht mehr benutzt wurden. Durch das Auslagern wird zwar die Platte beschäftigt (was ansich keine CPU-Zeit kostet) aber wenn Linux die Pages gut wählt dann merken die Programme davon gar nichts. Ich gehe davon aus das die Page-Ersetzungs-Strategien von Linux ziemlich gut sind.

Schlecht verhalten tun sich Programme, welche einen großen Puffer alloziieren, diesen nutzen und etwas ausrechnen, für das Ergebnis aber keinen weiteren Puffer alloziieren, sondern nur den nicht mehr genutzten Teil freigeben. Sehr schlimm wird das, wenn im Puffer dann der letzte Teil behalten und der große erste Teil freigegeben wird.
Das wäre aber bei meinen Segmenten eventuell auch ein Problem. Der Heap eines Programms liegt in mehreren Segmenten für unterschiedliche Objekt-Größen (ich denke da so an vielleicht 6 bis 10 Segmente, also von 64 Bytes an für jede weitere 4er-Potenz ein extra Heap-Segment) um die interne Fragmentierung möglichst klein zu halten, damit wäre ein realloc recht aufwendig wenn dabei das Objekt in ein anderes Segment verschoben wird. Diese Probleme treffen aber auch Programme auf Flat-Memory-Systemen, okay da kann man immerhin mitten aus dem virtuellen Raum einfach ein paar Pages freigeben falls diese komplett unbenutzt sind. Wenn wir von einem Programm ausgehen das sehr viele kleine Blöcke, mit leicht unterschiedlicher Größe, alloziert und in ungeordneter Reihenfolge teilweise wieder frei gibt dann dürften ziemlich viele Pages nur teilweise benutzt sein also das Programm deutlich mehr virtuellen Speicher belegen als eigentlich notwendig währe. Diese innere Fragmentierung in Grenzen zu halten ist Aufgabe der malloc-Implementierung in der libc (da kann ich aus einem reichhaltigem Fundus unterschiedlichster Ansätze und Forschungstheorien wählen), gegen die externe Fragmentierung hat mein OS die Segmente die es nach belieben Defragmentieren kann.
Da jedes Segment für sich unabhängig wachsen kann hab ich mit den mehreren Heap-Segmenten auch keine Probleme, wenn ein Flat-Memory-Programm in seinem einen virtuellen Adressraum mehrere Bereiche für unterschiedlich große Objekte vorsieht (aus denen malloc/realloc sich bedienen) dann könnte es passieren das dieser Bereich überproportional wächst und ihm ein anderer Bereich im Weg ist (die libc weiß ja nicht was das Programm für Objekte erstellt, es gibt bestimmt auch Programme wo 90% des Speicherbedarfs auf Objekte mit immer gleicher Größe entfällt). Bei meinem Konzept kann jedes Segment für sich und unabhängig wachsen, nur der verfügbare RAM (+ Swap-Space) geben da die Grenzen vor. Dieses Flat-Memory-Problem tritt auch bei Programmen mit sehr vielen Threads auf, wenn dann mal einer dieser Threads sehr viel Stack benötigt kann es sein das in dem virtuellen Adressraum wo der Stack hinwachsen möchte schon ein anderer Stack liegt. Bei meinen Heap-Segmenten soll meine libc auch das Schrumpfen unterstützen falls dort am Ende wieder viel freier Speicher entsteht, bei den Stack-Segmenten bin ich mir nicht sicher ob der OS-Kernel das können soll (dazu müsste der Stack-Pointer beobachtet werden). Mit meinen dynamischen Segmenten umgehe ich die klassischen Probleme des Speicher-Layouts von Flat-Memory-Programmen.

Das ist den uClibc-Leuten aufgefallen und stellt für diese ein großes Problem dar.
Okay, uCLinux hat schon aufgrund seines Konzeptes ein paar Probleme mehr. Da werden die Nachteile aus verschiedenen Welten kombiniert (und die jeweiligen Vorteile beseitigt), Speicherverwaltung ganz ohne Hardwareunterstützung ist eben nicht so einfach.

Achso, naja ich gehe immer davon aus, dass ein Segment bei 0 anfängt (ist für mich einer der Hauptvorteile) und eine Segmentnummer dazukommt.
Das meine Segmente nicht bei 0 anfangen müssen ist eher ein nettes Zusatzfeature, außerdem hatte ich in den Descriptoren noch Platz. Die meisten Segmente werden wohl ihre Offsets ganz normal bei 0 anfangen lassen.

Wenn du den Zugriff auf programmfremde Segmente hardwareseitig unterbindest (Speicherschutz)
Ja, das habe ich vor. :-D

Ich kenne halt nur die simple Mechanik des 8086 ...
An den x86-Real-Mode denken die meisten Leute wenn das Wort "Segmentierung" fällt (falls sie alt genug sind). Die Möglichkeiten die bereits der 386-Protected-Mode bietet (auch wenn das nur ein bescheidener Vorgeschmack auf richtige Segmentierung ist) sind selbst unter den erfahrenen Programmierern der überwiegenden Mehrheit völlig unbekannt. Wenn ich mit Leuten über meine Ideen rede dann darf ich immer erst mal anfangen zu erklären was dynamische Segmentierung wirklich ist. :(

Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff,
Aber nur auf Speicher der auch wirklich ausgelagert wurde.

ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.
Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt. Ersteres bedeutet dass das Programm komplett lahmgelegt ist und Letzteres kann bedeuten das es ohne (spürbare) Einschränkungen weiter arbeitet. Für jeden Tastendruck in einem Office-Programm mal eben über 100 MBytes einzulesen (und andere 100 MBytes vorher rauszuwerfen) ist sicher nicht praktikabel.

Swapping ist eine Notlösung und war (auch) einen Grund für Paging überhaupt. Der einzige Vorteil mit Paging ist die feinere Granlarität und damit die Möglichkeit, geringe Knappheit gut abzufangen.
Paging ist IMHO die einzigste Möglichkeit Knappheit für alle Programme fair abzufangen.

Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.
Hängt vom Anwendungsfall ab.
Mein geplanter Anwendungsfall ist ein allgemein nutzbarer PC. Daher darf ich auch keine Annahmen über die Programme machen. Es muss z.B. auch möglich sein Segmente zu erstellen die größer als der physische RAM sind, dass das nicht unbedingt geschickt/performant ist ist mir klar.

Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon.
RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.

Ich hätte dann halt auf ein Zwischenmedium ausgelagert, du lagerst in den virtuellen Adressraum aus. Das Umsortieren bei zu sehr zerstückeltem RAM sollte dagegen auch im physischen Bereich nicht zu lange dauern, da es dort meist um viele sehr kleine Segmente geht, für die noch Platz vorhanden sollte. Sonst ist der RAM voll und es tritt Fall 1 ein.
Ich glaube Du gehst davon aus das jeder Aufruf von malloc in einem User-Space-Programm ein neues Segment liefert. Das wäre echte Speicher-Verschwendung und für das OS ziemlich aufwändig. Den Heap verwalten die Programme selber, mit ihrer libc (die natürlich auch von mir kommen muss), siehe oben. Außerdem würden mir dann ziemlich schnell die Einträge in der LDT ausgehen. Natürlich können die Programme für bestimmte Dinge auch zusätzliche Segmente anfordern, z.B. ein Verschlüsselungsprogramm könnte die Keys in einem extra Segment halten und dieses Segment gegen das Swappen sperren lassen damit die unverschlüsselten Keys nicht auf die Platte kommen. Die Segmente in meinem System werden im Durchschnitt eher ziemlich groß sein.

Wenn in der Zeit des Defragmentierens die Gesamtsystemleistung um 5-25% sinkt (du möchtest sicherlich kein 4stufiges Paging haben), stört das auch nicht weiter, wäre sogar eine starke Verbesserung gegenüber dem Halt der betreffenden Programme. Bei einer angenommenen Performance von 200 MIPS verlierst du dann vielleicht 20-30 MIPS, das merkt man nichtmal - solange man die Anwendung nicht auf die CPU zuschneidet (d.h. Echtzeitbetrieb).
Die Performance sinkt beim Defragmentieren nur in den betroffenen Segmenten und das ist natürlich nicht so arg schlimm (vor allem weniger schlimm als wenn alle Programme leiden müssten). Um diesen Verlust so klein wie nur möglich zu halten soll das OS möglichst große Pages verwenden (deshalb will ich möglichst viele Page-Größen unterstützen) und der flexible TLB (der mit allen Page-Größen gut zurecht kommt) soll sein übriges dazu tun. Im 32 Bit-System wird nur ein 2-stufiges Paging verwendet (vom 586 abgeschaut, nur mit mehr unterstützen Page-Größen). Bei der 64 Bit-Ausführung bin ich mir noch nicht sicher, mit einem 3-stufigem Paging läuft das auf 512 kByte als kleinste Page-Größe hinaus (ist dann schon recht oft eine gewisse Speicher-Verschwendung) und mit nem 4-stufigem Paging komme ich auf 64 kByte als kleinste Page-Größe (was im Hinblick auf den Verschnitt schon etwas freundlicher aussieht, aber wohl etwas schlechter performt). Ich möchte mit den guarded-Page-Tables aber Abkürzungen durch die Tabellen ermöglichen, so das die 4 Stufen (bei den 64 kByte-Pages) sich auf 2 reduzieren lassen wenn nur 1 TByte oder weniger virtueller Speicher benötigt wird.
Ob ich im Spartan 6 überhaupt annähernd 200 MIPS erreiche wage ich stark zu bezweifeln, ich hoffe auf etwa 50 MIPS zu kommen und damit einem 486 mit 100 MHz Paroli bieten zu können, das erfordert natürlich leistungsfähige Befehle und einen guten Compiler. Ich denke gegen den 486 werden mein riesiges Register-File und die 3-Operanden-Befehle den entscheidenden Vorteil bringen, die 486 hatten noch kein Register-Renaming und ein damit verbundenes großes physisches Register-File, so das ich für den selben Algorithmus deutlich weniger Speicherzugriffe und MOV-Befehle benötige.

Warum sollte das mit mehreren CPUs schwerer werden?
Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).
Ja. Ich möchte die betreffende Page (zerstückelt in die kleinste Page-Größe) zum Verschieben als nicht-präsent markieren, alle anderen CPUs per Broadcast-Message informieren diesen Bereich aus dem TLB zu tilgen, den Inhalt kopieren und als letztes die neue physische Adresse in die Page-Table Eintragen und wieder als präsent markieren. Falls während der kurzen Zeit des umkopierens auf einer der anderen CPUs eine Page-Not-Present-Exception auftritt dann wird der Exception-Handler im Paging-Directory feststellen was Sache ist und einfach aktiv warten (pollen) bis der Kopier-Vorgang abgeschlossen ist (also die Page wieder benutzbar ist) und dann wieder zum Programm zurückkehren, wenn die CPU dann den Speicherzugriff noch mal probiert geht wieder alles. Das Programm (bzw. der eine Thread) friert für nicht mal eine Millisekunde (bei 100 MByte/s und 4 kByte/Page sind das 40 µs) ein und schon geht es weiter, ich denke das ist tolerabel und auch sicher recht selten (wäre schon ein guter Zufall wenn das Programm genau in dem Moment was von genau der Page will wo diese gerade verschoben wird).

Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst.
Hä?? Was hat defragmentieren mit sortieren zu tun und warum soll das genau 2 Segmente betreffen? Es ist durchaus möglich das mehrere Segmente der Defragmentierung bedürfen da sie vor kurzem vergrößert werden sollten aber nicht wachsen konnten weil dahinter schon was anderes lag (und kurzerhand in den virtuellen Speicher umgemappt wurden).

Paging kostet ja nur deshalb so viel, weil die Pagingtabelle im RAM liegt und du dadurch Unmengen an zusätzlichen RAM-Zugriffen hast, die vom Cache nur ungenügend abgefedert werden können. Oder seh ich da grundsätzlich was falsch?
Das siehst Du ganz genau richtig.
Ich frage mich wie das wird wenn wir tatsächlich etliche TByte RAM haben und Programme benutzen die das auch nutzen (wollen), dann sind die virtuellen Adressräume bei den Flat-Memory-Systemen nicht mehr so dünn besetzt und damit einige Abkürzungen durch die Paging-Tabellen nicht mehr möglich, auch das Konzept des Itanium dürfte dann problematisch werden.

Naja, wenn du keinen Speicherschutz hast, dann kann sich jedes Programm eine Segmentnummer zusammenbauen und dort zugreifen.
Richtig guter Speicherschutz ist das Kern-Ziel welches ich mit meiner Segmentierung erreichen will. :-D

Wenn du das nicht möchtest, dann musst du bei jedem Segmentzugriff nachschauen, ob das betreffende Segment in der weißen Liste des Prozesses steht...
Jain, die LDT wird natürlich nicht bei jedem Speicherzugriff befragt sondern nur wenn ein Selector in ein Segment-Register geladen werden soll bzw. wenn der Segment-Descriptor in die zugehörigen Shadow-Register geladen/decodiert wird. Eine Leaf-Funktion die nur mit den als Parameter übergebenen Pointen und globalen Variablen arbeitet braucht sowas überhaupt gar nicht zu tun. Auch sonst werde ich alles unternehmen damit die LDT möglichst selten benötigt wird.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 29. March 2010, 22:08
Hallo,

Um also ein paar MB umherzuschieben
Die Segmente können schon richtig groß werden, es hindert doch niemand die Programme daran mal ordentlich Speicher zu allozieren. Zumindest dürfen speicherhungrige Programme kein KO-Kriterium für mein Konzept sein.
Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.

Wenn du natürlich möglichst fleißig Segmente zusammenfassen möchtest, damit die Segmente möglichst groß werden und du innerhalb der Segmente den Verwaltungsaufwand hast, dann kommt zu der externen Fragmentierung (Segmente im physischen Adressraum) auch noch interne Fragmentierung (malloc/realloc in den Segmenten) dazu. Stell ich mir unangenehm vor.

Das System sollte an sich schon nicht stehenbleiben,
Aber die Auswahl an lauffähigen Prozessen könnte schon erheblich eingeschränkt sein. Außerdem dürften vor allem Programme die gerade im User-Focus stehen (und daher auf User-Aktionen reagieren müssen) am ehesten ihre Segmente vergrößern wollen und da fallen dem User Totzeiten natürlich als erstes auf.
Das meinte ich mit begrenzter Echtzeitfähigkeit. Den Programmen das beizubringen ist ja kein Problem, gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).

Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.
Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.

Den Garbage-Collector in die Idlezeit zu verlegen mag übrigens auch gut gehen, und in einem normalen System sind die meisten Anwendungen eh nicht aktiv. Sie werden nur aktiv, wenn sie vom System (oder von der Hardware) gerufen werden; bei einer Uptime von nem knappen Monat finden sich bei mir einige Prozesse mit CPU-Zeit von Sekunden. Die kann man prinzipiell immer verschieben.

Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien, ansonsten kannst du die natürlich jederzeit auch garantieren. (Wobei garantierter Leerlauf eher nach Überhitzungsschutz klingt...)

Wenn meine Festplatte beschäftigt ist (oder der Auslagerungsmarathon beginnt), dann steht mein Linux auch komplett.
Also mein Linux steht da nicht komplett, das Linux versucht doch als erstes Pages auszulagern die schon eine Weile nicht mehr benutzt wurden. Durch das Auslagern wird zwar die Platte beschäftigt (was ansich keine CPU-Zeit kostet) aber wenn Linux die Pages gut wählt dann merken die Programme davon gar nichts. Ich gehe davon aus das die Page-Ersetzungs-Strategien von Linux ziemlich gut sind.
Das mag sein, aber wenn die Maus ruckelt und auf Tastendrücke keine Reaktion kommt, dann steht für den Nutzer das System (bis der OOM-Killer anläuft). Ansonsten tritt das Phänomen nicht auf. In dem Augenblick, wo Pages ausgelagert werden müssen, die noch gebraucht werden, ist ohnehin alles verloren. Bis zu diesem Zeitpunkt kann man Buffers/Caches einfach wegwerfen.

Das ist den uClibc-Leuten aufgefallen und stellt für diese ein großes Problem dar.
Okay, uCLinux hat schon aufgrund seines Konzeptes ein paar Probleme mehr. Da werden die Nachteile aus verschiedenen Welten kombiniert (und die jeweiligen Vorteile beseitigt), Speicherverwaltung ganz ohne Hardwareunterstützung ist eben nicht so einfach.
Besser als uClinux geht es fast nicht. Trotz der ganzen Probleme, die mit der Speicherfragmentierung einhergehen, läuft das System trotzdem. Gut, in der Regel mit relativ einfachen und kleinen Programmen, aber "out of memory" bei zerstückeltem RAM gibt es selten unter 75% RAM-Auslastung. Und solche Situationen merkt man auch schon früher als Fall für den Garbage Collector.

Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff,
Aber nur auf Speicher der auch wirklich ausgelagert wurde.
Nicht während des Aus- und wieder Einlagerns. Die halte ich für das größte Problem, denn während der Kernel sein Speichermanagement mit der Festplatte abquatschen muss, geht nicht wirklich viel. Besonders, wenn die Page, in der der Taskzustand steht, fehlt und das Programm nicht als schlafend markiert werden kann. Ich halte das Speichermanagement für zu kritisch, als dass man nebenher die unbelasteten Anwendungen weiterlaufen lassen könnte.

Sicher, alle anderen Programme können am Ende des Marathons weiterlaufen, das Problem sehe ich heutzutage aber eher darin, dass irgendein Programm amok läuft, schnell viel RAM füllt, das System in die Ecke drängt bis es dann vom Kernel OOM-gekillt wird.

Swap als Arbeitsgrundlage braucht man mMn nicht mehr. Dazu ist RAM zu billig geworden.

ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.
Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt.
Muss das Programm denn nur aus drei Segmenten bestehen? Wer hindert dich denn daran, für die Eventhandler ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?

Ersteres bedeutet dass das Programm komplett lahmgelegt ist und Letzteres kann bedeuten das es ohne (spürbare) Einschränkungen weiter arbeitet. Für jeden Tastendruck in einem Office-Programm mal eben über 100 MBytes einzulesen (und andere 100 MBytes vorher rauszuwerfen) ist sicher nicht praktikabel.
Der Tastendruck gehört entweder woanders verarbeitet, oder aber das Office-Paket nicht ausgelagert, wenn es eh noch benutzt wird.

Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.
Hängt vom Anwendungsfall ab.
Mein geplanter Anwendungsfall ist ein allgemein nutzbarer PC. Daher darf ich auch keine Annahmen über die Programme machen. Es muss z.B. auch möglich sein Segmente zu erstellen die größer als der physische RAM sind, dass das nicht unbedingt geschickt/performant ist ist mir klar.
Außerdem gehst du davon aus, dass die Programme von der Segmentierung nichts wissen (oder mitkriegen können), und somit dein System nicht entlasten können. Das sehe ich wie, wenn sich zwei Deutsche auf Englisch unterhalten - es funktioniert, ist aber schon im Ansatz falsch. Wenn du den Programmen keine segmentfreundliche Nutzung zugestehst, dann kann das nicht performant werden, einfach weil du auf der falschen Architektur arbeitest.

Heutzutage ist es ja (für größere Projekte) auch üblich, dass man möglichst nicht regelmäßig den Cache kaputtmacht. Solche Rücksichtnahme auf die reale Architektur muss vorhanden sein...

Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon.
RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.
Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen. Und bei genügend RAM (oder nicht genügend RAM-Auslastung) gibt es keinen Grund zu defragmentieren.

Die Segmente in meinem System werden im Durchschnitt eher ziemlich groß sein.
Womit du innerhalb der Segmente den Aufwand hast. Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...

Warum sollte das mit mehreren CPUs schwerer werden?
Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).
Ja. ...
Klingt gut. Wobei ich die möglicherweise auftretenden Probleme bei mehreren CPUs weder ab- noch einschätzen kann.

Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst.
Hä?? Was hat defragmentieren mit sortieren zu tun und warum soll das genau 2 Segmente betreffen? Es ist durchaus möglich das mehrere Segmente der Defragmentierung bedürfen da sie vor kurzem vergrößert werden sollten aber nicht wachsen konnten weil dahinter schon was anderes lag (und kurzerhand in den virtuellen Speicher umgemappt wurden).
Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.

Was ich damit grundsätzlich sagen möchte, wenn du ohne Paging auskommen möchtest (es im Normalfall also eh nicht nutzen möchtest), dann brauchst du auch nicht die volle Flexibilität von Paging. Verschwendetes Silizium.

Für mich klingt es nach einer ausgewachsenen Nutzung von Paging und Segmentierung (was ich persönlich auch sehr gut finde) mit dem Designziel, die Pagingeinheit grundsätzlich abgeschaltet zu lassen. Nur wozu dann vollwertig implementieren? Außerdem rede ich (für meine Begriffe) immer von Randbedingungen, seltenen Ereignissen - die zu optimieren bringt mMn wenig.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 30. March 2010, 17:15
Hallo,


Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.
Wieso sollte die Fragmentierung bevorzugt mit kleinen Segmenten auftreten? Gerade für kleine Segmente wird sich doch noch eher eine passende Lücke finden lassen als für große Segmente. Ein Segment das nur eine Page (kleinste Page-Größe) belegt kann gar nicht fragmentieren.

Wenn du natürlich möglichst fleißig Segmente zusammenfassen möchtest, damit die Segmente möglichst groß werden und du innerhalb der Segmente den Verwaltungsaufwand hast, dann kommt zu der externen Fragmentierung (Segmente im physischen Adressraum) auch noch interne Fragmentierung (malloc/realloc in den Segmenten) dazu. Stell ich mir unangenehm vor.
Ja, das möchte ich. Die interne Fragmentierung werde ich aber mit meinen unabhängigen dynamischen Segmenten deutlich besser in den Griff bekommen als das ein Flat-Memory-Programm je könnte.

gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).
Die Segmente für .code , .const und .data sind in ihrer Größe fest, die werden von hinten anfangend in den physischen Speicher gepackt. Die Heap-Segmente und Stack-Segmente sind flexibel und kommen von vorne anfangend in den physischen Speicher. Der Micro-Kernel in meinem OS weiß nicht was ein Treiber ist, der wird alle Programme gleich gut behandeln. Es wird aber eine Möglichkeit geben Segmente zu locken damit sie garantiert nicht verschoben werden (Treiber werden das nutzen), so ein Segment wird dann auch sofort nach vorne verlegt (der Treiber blockiert also während seiner Initialisierung ein bisschen wegen dem sofortigem verschieben oder das Segment wird gleich als gelockt erstellt), damit ist das Segment aber auch nicht mehr in seiner Größe änderbar.

Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.
Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.
Das hängt sehr davon ab wie neue Programme gestartet und beendet werden oder die Programme ihre (Heap-/Stack-)Segmente vergrößern/schrumpfen. Solange die RAM-Auslastung unter 50% bleibt sollte Defragmentieren kaum nötig sein, wird aber trotzdem mal auftreten (vor allem beim wachsen von Segmenten, da kann immer mal was anderes im Weg sein).

Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,
Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!

ansonsten kannst du die natürlich jederzeit auch garantieren.
Wie? Mal abgesehen davon das sowas IMHO Quatsch ist.

(Wobei garantierter Leerlauf eher nach Überhitzungsschutz klingt...)
lol :-D

In dem Augenblick, wo Pages ausgelagert werden müssen, die noch gebraucht werden, ist ohnehin alles verloren.
Das soll ja die Page-Ersetzungs-Strategie möglichst verhindern. Die ist natürlich auch nicht perfekt und dann gibt es ein paar kurze Wartezeiten (wobei wenn die Page-Not-Present-Exception noch während des Schreibens auf die Festplatte kommt könnte der Exception-Handler die Page einfach wieder als gültig markieren und den Schreibvorgang als ungültig), aber im Durchschnitt wird die schon gut funktionieren. Wenn es gar nicht mehr anders geht fliegen IMHO die Programme mit der niedrigsten Priorität als erstes raus (die habens dann natürlich extrem schwer).

Besser als uClinux geht es fast nicht. Trotz der ganzen Probleme, die mit der Speicherfragmentierung einhergehen, läuft das System trotzdem.
Klar, in Anbetracht der Möglichkeiten die uCLinux hat ist das Ergebnis natürlich hervorragend.

aber "out of memory" bei zerstückeltem RAM gibt es selten unter 75% RAM-Auslastung. Und solche Situationen merkt man auch schon früher als Fall für den Garbage Collector.
Bei mir wird dann das Defragmentieren anspringen wenn sich für ein neues Segment nicht mehr physischer Speicher am Stück findet oder wenn ein Segment nicht einfach wachsen konnte und deshalb physisch zerteilt werden musste. Andere Gründe für das Defragmentieren fallen mir nicht ein. Einen "Garbage Collector" wird es bei mir nicht geben, kann ich mir momentan auch nicht vorstellen was der machen sollte. Das einzigste was mir einfällt sind Programme die ihren Speicher freiwillig, auf Bitte vom OS-Kernel, wieder hergeben. Ein File-System-Cache wäre da ein gutes Beispiel, normale User-Programme trifft das eher nicht.

denn während der Kernel sein Speichermanagement mit der Festplatte abquatschen muss, geht nicht wirklich viel.
Das "abquatschen" (Verwaltungsstrukturen im Speicher ändern) dürfte bei der Performance heutiger PC-CPUs vernachlässigbar sein und der eigentliche Datentransfer wird vom Festplatten-Host-Controller selbstständig erledigt, da kann die CPU sich längst wieder um die Programme kümmern.

Besonders, wenn die Page, in der der Taskzustand steht, fehlt
Ein OS das seine eigenen Verwaltungsstrukturen auslagert hat es auch nicht besser verdient. ;)

Ich halte das Speichermanagement für zu kritisch, als dass man nebenher die unbelasteten Anwendungen weiterlaufen lassen könnte.
Wieso? Wenn man alles ordentlich mit spinlocks o.ä. absichert sehe ich da keine Probleme. Ein passendes Konzept zu entwerfen ist sicher nicht trivial (das werde ich bestimmt früher spüren als mir lieb ist) aber nicht unmöglich. Im Linux-Kernel versucht man doch auch diesen "Big-Kernel-Lock" zu beseitigen damit der Kernel auch unter Last noch die Wünsche der Programme befriedigen kann, gerade auf einem massiven SMP-System ist das nötig.

das Problem sehe ich heutzutage aber eher darin, dass irgendein Programm amok läuft, schnell viel RAM füllt, das System in die Ecke drängt bis es dann vom Kernel OOM-gekillt wird.
Solche Programme sind natürlich ein Ärgernis. Trotzdem soll das OS auch damit einigermaßen geschickt umgehen können. Hier wären vielleicht Memory-Quotas eine Lösung. Sowas wurde, hier im Forum, schon mal diskutiert http://lowlevel.brainsware.org/forum/index.php?topic=2256. Wenn mein OS mit Amok laufenden Programmen einigermaßen (also besser als Windows) umgehen kann dann bin ich zufrieden.

Swap als Arbeitsgrundlage braucht man mMn nicht mehr. Dazu ist RAM zu billig geworden.
ACK
Aber ganz auf Swapping verzichten kann/darf man trotzdem nicht (außer im Embedded-Bereich wo man vorher weiß welche Programme laufen werden). Swapping muss in meinem Konzept ordentlich vorgesehen sein, auch wenn ich das sicher nicht gleich beim ersten Anlauf umsetzen werde.

ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.
Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt.
Muss das Programm denn nur aus drei Segmenten bestehen?
Nein, die Programme werden schon ein paar Segmente mehr benötigen. Aber da die Größe der Segmente wohl in Pages (Größe der kleinsten unterstützen Page, in der 64 Bit-Variante also mindestens 64 kBytes) gemessen wird, entsteht eine Menge Verschnitt wenn man alles auf zu viele kleine Segmente verteilt. Außerdem kann die LDT maximal 32767 Segmente aufnehmen, damit will ich schon etwas sparsam umgehen.

ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?
Wieso sollten kleine Segmente nicht ausgelagert werden?

Der Tastendruck gehört entweder woanders verarbeitet
Und Wo? Wenn nicht im Office-Paket?

oder aber das Office-Paket nicht ausgelagert
Genau das ist der Punkt wo ich sage das Paging die einzigste Möglichkeit ist um Knappheit für alle Programme gleich fair abzufangen. Man kann doch schließlich nicht große Programme komplett im RAM lassen nur weil ein kleiner Teil davon benötigt wird. Ich denke jedes Programm muss etwas geben damit auch bei (leichter) Knappheit alle Programme noch einigermaßen arbeiten können.

Außerdem gehst du davon aus, dass die Programme von der Segmentierung nichts wissen (oder mitkriegen können), und somit dein System nicht entlasten können. Das sehe ich wie, wenn sich zwei Deutsche auf Englisch unterhalten - es funktioniert, ist aber schon im Ansatz falsch. Wenn du den Programmen keine segmentfreundliche Nutzung zugestehst, dann kann das nicht performant werden, einfach weil du auf der falschen Architektur arbeitest.
Ja, Du hast (in gewissen Grenzen) recht. Solange Segmentierung allgemein belächelt wird hab ich aber keine andere Wahl. Wirklich alles selber programmieren will ich nicht, ich will auch mal ein nützliches Tool portieren können. Natürlich muss die libc genau wissen wie die Segmente zu nutzen sind, auch all die anderen Librarys welche die Dienste des OS, oder der Personality, dem eigentlichen User-Space-Programm zugänglich machen sollten wissen das sie es mit Segmentierung zu tun haben. Wenn ich mir normale C-Programme ansehe dann gehen die erstmal von gar keiner Architektur aus, ich glaube auch nicht das in den C/C++-Specs dazu was drin steht. Es ist Aufgabe des Compilers die tatsächliche Ziel-Architektur angemessen (also möglichst optimal) zu bedienen.

Wie sollten den normale User-Programme mithilfe der Segmentierung mein System entlasten?

Heutzutage ist es ja (für größere Projekte) auch üblich, dass man möglichst nicht regelmäßig den Cache kaputtmacht. Solche Rücksichtnahme auf die reale Architektur muss vorhanden sein...
Da gebe ich Dir natürlich recht. Rücksicht auf Caches und dergleichen nehmen die Programmierer aber nicht für das Betriebssystem oder die Plattform sondern für die Performance des Programms, also aus Eigennutz. Die Existenz und Wirkungsweise von Caches hat physische Ursachen um die auch ich nicht herum komme, so das all diese Tricks eben auch auf meiner Plattform funktionieren werden (wenn auch auf einem deutlich niedrigerem Niveau).

RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.
Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.
Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.

Und bei genügend RAM (oder nicht genügend RAM-Auslastung) gibt es keinen Grund zu defragmentieren.
Jedenfalls nur sehr selten.

Womit du innerhalb der Segmente den Aufwand hast.
So wie in einem Flat-Memory-Programm auch, nur das ich mit den unabhängigen Segmenten ein paar Freiheitsgrade mehr hab um die interne Fragmentierung im Zaum zu halten.

Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...
Hä? FAR-Pointer brauch ich doch trotzdem, der Compiler weiß doch nicht vorher wo die Speicherblöcke real liegen. Ein Pointer hat genau drei verschiedene Quellen: 1. der Compiler muss eine statische Variable referenzieren (aus .const oder .data, das passiert beim Linken), oder der Compiler muss eine lokale Variable referenzieren (also die Adresse von etwas ermitteln das auf dem Stack liegt, dafür muss der Compiler etwas Code erzeugen) und 3. kommt als Rückgabewert von malloc/realloc (das ist mein Part, ob ich da unbedingt Assembler für benötige weiß ich noch nicht). Alles andere ist reine Pointer-Arithmetik.

Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.
Wieso "Quellsegment" und "Zielsegment"? Ich kopiere doch nicht den Inhalt von einem Segment in ein anderes sondern verschiebe (in Wirklichkeit natürlich kopieren) die Pages aus denen ein Segment besteht. Das Segment befindet sich für diesen Vorgang zwar im virtuellen Speicher und seine Pages sing möglicherweise total über den physischen Speicher zerstreut, aber es ist trotzdem nur ein Segment an dem gerade gearbeitet wird.

Was ich damit grundsätzlich sagen möchte, wenn du ohne Paging auskommen möchtest (es im Normalfall also eh nicht nutzen möchtest), dann brauchst du auch nicht die volle Flexibilität von Paging. Verschwendetes Silizium.
Ohne ein vollwertiges Paging bringt mir die Segmentierung aber zu viele Nachteile (zum DOS will ja niemand zurück). Natürlich kostet das Silizium aber ohne kommt kein gescheites System bei raus.

Für mich klingt es nach einer ausgewachsenen Nutzung von Paging und Segmentierung (was ich persönlich auch sehr gut finde) mit dem Designziel, die Pagingeinheit grundsätzlich abgeschaltet zu lassen.
Ganz genau. Ich will das Paging haben weil ich damit ne Menge Magie veranstalten kann aber ich will es nicht ständig benutzen müssen weil es Performance kostet. Wenn ich auf die Magie verzichte dann kostet das aber noch mehr Performance (z.B. atomares Verschieben von Segmenten ist IMHO ein absolutes KO-Kriterium).

Außerdem rede ich (für meine Begriffe) immer von Randbedingungen, seltenen Ereignissen - die zu optimieren bringt mMn wenig.
Das Defragmentieren soll natürlich möglichst selten vorkommen aber wenn es dann, wenn es gebraucht wird, nicht richtig funktioniert ist IMHO mein Konzept totaler Mist (mal abgesehen davon das die meisten Leute die Segmentierung eh als totalen Mist bewerten).


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Svenska am 31. March 2010, 14:28
Hallo,

Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.
Wieso sollte die Fragmentierung bevorzugt mit kleinen Segmenten auftreten? Gerade für kleine Segmente wird sich doch noch eher eine passende Lücke finden lassen als für große Segmente. Ein Segment das nur eine Page (kleinste Page-Größe) belegt kann gar nicht fragmentieren.
Das Problem sind große Segmente, die zu (sehr) kleinen Segmenten schrumpfen, beispielsweise ein Netzwerkpuffer, in dem Netzwerkdaten gesammelt werden, bis dann nach dem Header die Nutzdaten kommen und der Header weggeschnitten wird. Dann hast du nämlich ein kleines Loch vor oder hinter dem nächsten Segment. Mach das so lange, bis du den Speicher voll hast mit kleinen Segmenten, die von kleinen Löchern umgeben sind und der nächste (große) Puffer kann nicht mehr alloziiert werden.

Große Segmente, die groß bleiben (oder kleine Segmente, die nur kurz gebraucht werden) spielen in der Fragmentierung keine Rolle. Wenn doch, hast du zuwenig RAM.

gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).
Die Segmente für .code , .const und .data sind in ihrer Größe fest, die werden von hinten anfangend in den physischen Speicher gepackt. Die Heap-Segmente und Stack-Segmente sind flexibel und kommen von vorne anfangend in den physischen Speicher. Der Micro-Kernel in meinem OS weiß nicht was ein Treiber ist, der wird alle Programme gleich gut behandeln. Es wird aber eine Möglichkeit geben Segmente zu locken damit sie garantiert nicht verschoben werden (Treiber werden das nutzen), so ein Segment wird dann auch sofort nach vorne verlegt (der Treiber blockiert also während seiner Initialisierung ein bisschen wegen dem sofortigem verschieben oder das Segment wird gleich als gelockt erstellt), damit ist das Segment aber auch nicht mehr in seiner Größe änderbar.
Genau das meinte ich. Und wenn du systemkritische Segmente in der Form erzeugen kannst, werden die auch von paginglosem Defragmentieren nicht betroffen sein.

Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.
Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.
Das hängt sehr davon ab wie neue Programme gestartet und beendet werden oder die Programme ihre (Heap-/Stack-)Segmente vergrößern/schrumpfen. Solange die RAM-Auslastung unter 50% bleibt sollte Defragmentieren kaum nötig sein, wird aber trotzdem mal auftreten (vor allem beim wachsen von Segmenten, da kann immer mal was anderes im Weg sein).
Ja, und solange es selten auftritt... :-) Das ist ja meine Ausgangssituation.

Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,
Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!
Klar, läuft bei mir. Und?

ansonsten kannst du die natürlich jederzeit auch garantieren.
Wie? Mal abgesehen davon das sowas IMHO Quatsch ist.
Wenn das Betriebssystem sich eine gewisse CPU-Zeit zur Selbstverwaltung genehmigt, sehe ich darin kein Problem. Damit könnte man dann sogar weiche Echtzeitanforderungen bearbeiten; harte Echtzeit kriegst du auf einem Allzweck-System ohnehin nicht hin.

Swap als Arbeitsgrundlage braucht man mMn nicht mehr. Dazu ist RAM zu billig geworden.
ACK
Aber ganz auf Swapping verzichten kann/darf man trotzdem nicht (außer im Embedded-Bereich wo man vorher weiß welche Programme laufen werden). Swapping muss in meinem Konzept ordentlich vorgesehen sein, auch wenn ich das sicher nicht gleich beim ersten Anlauf umsetzen werde.

ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?
Wieso sollten kleine Segmente nicht ausgelagert werden?
Nun, die könnte man locken...

Der Tastendruck gehört entweder woanders verarbeitet
Und Wo? Wenn nicht im Office-Paket?
Im Hauptprogramm aus 50 MB Code? Im Datensegment aus 400 MB Daten? Oder vielleicht doch in einem Event-Handler-Segment (bevorzugt gelockt)?

Ich denke jedes Programm muss etwas geben damit auch bei (leichter) Knappheit alle Programme noch einigermaßen arbeiten können.
Ich sehe leiche Knappheit als nicht vorhanden. Entweder die Ressourcen (bes. RAM) sind da, oder sie fehlen komplett. Bei Speichergrößen im GB-Bereich sehe ich ein paar fehlende KB als sehr unwahrscheinlich an. Und wenn es im Zig-MB-Bereich liegt, dann gibt das bereits spürbare Probleme, weil die meisten Programme, die viel RAM belegen, eben auch viel RAM nutzen.

Wie sollten den normale User-Programme mithilfe der Segmentierung mein System entlasten?
Wenn sie gewisse Rücksicht auf das System nehmen, z.B. Eventhandler in eigene gelockte Segmente verlagern oder Segmente eben nicht stark verkleinern, sondern wegwerfen und sich ein kleines Segment nehmen, dann entlastet das die Verwaltung auch. Du versuchst ja auch nicht, ein Serversystem auf 'nem DSP laufenzulassen. Der ist dafür halt nicht gebaut. (Es geht, Linux läuft ja auf manchen. Deswegen ist es trotzdem nicht das geeignetste System.)

Da gebe ich Dir natürlich recht. Rücksicht auf Caches und dergleichen nehmen die Programmierer aber nicht für das Betriebssystem oder die Plattform sondern für die Performance des Programms, also aus Eigennutz.
Haste Recht. Doof, so ein Egoismus. :x Trotzdem sollte man schon einigermaßen dafür sorgen, dem System nicht mehr Last aufzubrummen als notwendig.

Gegenbeispiel dafür ist Java. Hauptsache langsam. Oder Flash.

RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.
Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.
Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.
Auf einer segmentierten Architektur führt Fragmentierung dazu, dass du keine großen, zusammenhängenden freien Speicherblöcke mehr hast. Durch Defragmentierung kannst du durch Umschichten der Segmente solche Blöcke erzeugen, die - je nach Stückelung der vorhandenen Segmente - genügend große Blöcke erzeugen und damit das Swapping nicht erforderlich machen.

Fragmentierung ist eine Stufe der RAM-Knappheit (es ist kein nutzbarer Speicher da), im Gegensatz zum Swapping (es ist kein Speicher da).

Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...
Hä? FAR-Pointer brauch ich doch trotzdem, der Compiler weiß doch nicht vorher wo die Speicherblöcke real liegen. Ein Pointer hat genau drei verschiedene Quellen: 1. der Compiler muss eine statische Variable referenzieren (aus .const oder .data, das passiert beim Linken), oder der Compiler muss eine lokale Variable referenzieren (also die Adresse von etwas ermitteln das auf dem Stack liegt, dafür muss der Compiler etwas Code erzeugen) und 3. kommt als Rückgabewert von malloc/realloc (das ist mein Part, ob ich da unbedingt Assembler für benötige weiß ich noch nicht). Alles andere ist reine Pointer-Arithmetik.
Wenn die verwendeten Segmentnummern zum Task gehören, braucht das Programm sich im Regelfall nicht um FAR-Pointer scheren (ein Datenzugriff geht nach .data, ein Codezugriff nach .code usw). Problematisch wird es erst, wenn du Segmentregister umladen musst - also FAR-Pointer dereferenzierst - denn das lässt sich nicht vermeiden. Klingt das überhaupt logisch?...

Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.
Wieso "Quellsegment" und "Zielsegment"? Ich kopiere doch nicht den Inhalt von einem Segment in ein anderes sondern verschiebe (in Wirklichkeit natürlich kopieren) die Pages aus denen ein Segment besteht. Das Segment befindet sich für diesen Vorgang zwar im virtuellen Speicher und seine Pages sing möglicherweise total über den physischen Speicher zerstreut, aber es ist trotzdem nur ein Segment an dem gerade gearbeitet wird.
Du hast ein Quellsegment, welches du verschieben möchtest und (ein oder mehrere) Zielsegmente, die da liegen, wo du das Quellsegment hinhaben möchtest. Die musst du vorher also sichern, ob im virtuellen Adressraum, (im physischen Adressraum, sofern genug Platz) oder auf Platte. So viele müssen das nicht unbedingt sein, außerdem muss oder sollte man große Segmente eh nur selten verschieben müssen, eher die kleinen drum herum.

Was ich damit grundsätzlich sagen möchte, wenn du ohne Paging auskommen möchtest (es im Normalfall also eh nicht nutzen möchtest), dann brauchst du auch nicht die volle Flexibilität von Paging. Verschwendetes Silizium.
Ohne ein vollwertiges Paging bringt mir die Segmentierung aber zu viele Nachteile (zum DOS will ja niemand zurück). Natürlich kostet das Silizium aber ohne kommt kein gescheites System bei raus.
Naja, ich würde mit dem Paging gezielt die Probleme der Segmentierung abschwächen/entfernen wollen. Wenn das Konzept sich dann als nicht tragbar erweist, kann man sich immernoch um was anderes bemühen - oder Segmentierung endgültig als Mist abstempeln.

Für mich klingt es nach einer ausgewachsenen Nutzung von Paging und Segmentierung (was ich persönlich auch sehr gut finde) mit dem Designziel, die Pagingeinheit grundsätzlich abgeschaltet zu lassen.
Ganz genau. Ich will das Paging haben weil ich damit ne Menge Magie veranstalten kann aber ich will es nicht ständig benutzen müssen weil es Performance kostet. Wenn ich auf die Magie verzichte dann kostet das aber noch mehr Performance (z.B. atomares Verschieben von Segmenten ist IMHO ein absolutes KO-Kriterium).
Naja, dann ist das eben so.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 31. March 2010, 19:14
Hallo,


Das Problem sind große Segmente, die zu (sehr) kleinen Segmenten schrumpfen, beispielsweise ein Netzwerkpuffer, in dem Netzwerkdaten gesammelt werden, bis dann nach dem Header die Nutzdaten kommen und der Header weggeschnitten wird. Dann hast du nämlich ein kleines Loch vor oder hinter dem nächsten Segment. Mach das so lange, bis du den Speicher voll hast mit kleinen Segmenten, die von kleinen Löchern umgeben sind und der nächste (große) Puffer kann nicht mehr alloziiert werden.
Na und? So lange in Summe genügend freier Speicher vorhanden ist kann dieser auch für ein großes Segment alloziert werden. Dieses große Segment hat dann seine lineare Basis-Adresse nicht mehr direkt im physischen Speicher (weil es ja eben nicht mehr an einem Stück im physischen Speicher liegt) sondern im virtuellen Speicher (und dort werden die einzelnen physischen Stücke zu einem virtuellem Ganzen per Paging zusammengesetzt).
Der virtuelle Speicher ist ein Teilbereich des linearen Speichers aber es gibt dort keinen physischem Speicher, siehe http://lowlevel.brainsware.org/forum/index.php?topic=2470.msg27750#msg27750. Die dort gezeigte Memory-Map repräsentiert den linearen Speicher, überall dort wo auch physischer Speicher vorhanden ist (oder Memory-Mapped-I/O) ist der lineare Speicher 1:1 mit dem physischen Speicher identisch, nur dort wo unter dem linearen Speicher nichts liegt ist der virtuelle Bereich des linearen Speichers (nur dort ist das Paging aktiv). Ich weiß das ist etwas verwirrend und gänzlich anders als das was man sonst so kennt. Der lineare Speicher ist nur eine Abstraktion die entweder 1:1 oder per Paging auf den physischen Speicher verweist, die Segmente setzen immer auf den linearen Speicher auf damit man sie Defragmentieren und Swappen kann.

Aus meiner Sicht gibt es 2 Arten von Speicher-Defragmentierung in meinem System:Ich denke solange die Speicherauslastung gering ist dürfte die Variante 1 die bessere Wahl sein obwohl damit der freie physische Speicher immer mehr fragmentiert, um dem vorzubeugen bzw. echte Segment-Fragmentierung bei hoher Speicherauslastung zu bekämpfen muss man die 2. Variante benutzen.


Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.
Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.
Auf einer segmentierten Architektur führt Fragmentierung dazu, dass du keine großen, zusammenhängenden freien Speicherblöcke mehr hast.
Das stimmt, bedeutet aber nicht das der noch vorhandene freie Speicher nicht mehr nutzbar ist. Genau dafür (und zum Swappen) will ich ordentliches Paging haben.

Durch Defragmentierung kannst du durch Umschichten der Segmente solche Blöcke erzeugen, die - je nach Stückelung der vorhandenen Segmente - genügend große Blöcke erzeugen und damit das Swapping nicht erforderlich machen.
Solange noch genügend freier Speicher vorhanden ist, egal wie zerstreut der auch ist, kann ich den auch nutzen (Dank dem Paging). Swapping ist wirklich nur für tatsächliche RAM-Knappheit erforderlich.

Du hast ein Quellsegment, welches du verschieben möchtest und (ein oder mehrere) Zielsegmente, die da liegen, wo du das Quellsegment hinhaben möchtest. Die musst du vorher also sichern, ob im virtuellen Adressraum, (im physischen Adressraum, sofern genug Platz) oder auf Platte. So viele müssen das nicht unbedingt sein, außerdem muss oder sollte man große Segmente eh nur selten verschieben müssen, eher die kleinen drum herum.
Ein Festplatten-Defragmentierer kann die Dateien auch nicht wo anders hin auslagern, der braucht einfach nur mindestens einen freien Cluster zum arbeiten. Wie groß die Segmente sind, die defragmentiert werden müssen, spielt für den Defragmentierer keine Rolle.


Bei Speichergrößen im GB-Bereich sehe ich ein paar fehlende KB als sehr unwahrscheinlich an. Und wenn es im Zig-MB-Bereich liegt, dann gibt das bereits spürbare Probleme, weil die meisten Programme, die viel RAM belegen, eben auch viel RAM nutzen.
Wenn auf einem Rechner mit X GByte RAM gerade 32 kBytes fehlen dann ist es eben so und das OS muss schauen was es auslagern kann, was bei dieser kleinen Menge natürlich kein Problem ist. Wenn etliche MBytes fehlen dann muss das OS auch schauen was raus kann und da dürfte es in vielen Programmen einiges geben was nicht ständig gebraucht wird. Klar bei winzigen Programme die mit ein paar kBytes genau eine Aufgabe erledigen ist da wenig zu holen aber bei einem fetten Office-Paket dürfte da bestimmt einiges gehen.

... oder Segmente eben nicht stark verkleinern, sondern wegwerfen und sich ein kleines Segment nehmen, dann entlastet das die Verwaltung auch.
Wieso sollte das anlegen eines neuen Segmentes und das löschen eines vorhandenen Segmentes weniger kosten als das einfache schrumpfen eines vorhandenen Segmentes? (mal davon abgesehen das ersteres 2 SYSCALLs und letzteres nur 1 SYSCALL braucht) Beim Verkleinern entsteht eben eine neue Lücke bzw. eine vorhandene Lücke neben dem Segment wird größer, das neu erstellen (und Daten umkopieren?) ist IMHO einiges aufwendiger. Wenn sich für das neue Segment nicht zufällig eine genau passende Lücke findet dann bleibt auch wieder ne Lücke übrig.


Der Tastendruck gehört entweder woanders verarbeitet
Und Wo? Wenn nicht im Office-Paket?
Im Hauptprogramm aus 50 MB Code? Im Datensegment aus 400 MB Daten? Oder vielleicht doch in einem Event-Handler-Segment (bevorzugt gelockt)?
Für den Event-Handler extra Segmente anzulegen ist ne Verschwendung (Page-Verschnitt) und stelle ich mir außerdem schwierig/umständlich vor (der bräuchte ein eigenes Heap-System u.a.m., der Compiler müsste den Code/Daten nach X? Kriterien über mehrere Segmente verteilen). Genau da kann das Paging was rausreißen in dem es dafür sorgt das nur die Pages im RAM bleiben müssen die auch tatsächlich benötigt werden. Warum sollte ich die Programme komplexer machen wenn der gewünschte Effekt mit dem Swapping per Paging von ganz alleine kommt? Außerdem wäre diese Komplexität in den Programmen immer drin auch wenn gar nicht geswappt werden muss und swappen ist ja eher die Ausnahme als die Regel und für Ausnahmen muss man doch nicht unbedingt so sehr optimieren. Normale Segmente zu locken ist IMHO kontraproduktiv, das OS sollte selber entscheiden dürfen was es raus wirft und was nicht. Je mehr gelockt wird desto weniger Freiheiten hat das OS.


Und wenn du systemkritische Segmente in der Form erzeugen kannst, werden die auch von paginglosem Defragmentieren nicht betroffen sein.
Gelockte Segmente sind weder vom Defragmentieren noch vom Swapping betroffen und genau deshalb muss ich damit sehr sparsam sein sonst werden die normalen Programme zu arg blockiert. Sein Segment zu locken ist für die anderen Programme unfair (natürlich haben die trotzdem was von funktionierenden Triebern) und deshalb soll das nur gemacht werden wenn es tatsächlich zwingenst erforderlich ist.

ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?
Wieso sollten kleine Segmente nicht ausgelagert werden?
Nun, die könnte man locken...
Warum sollten normale User-Programme anfangen dürfen ihre Segmente einfach/unkontrolliert zu locken und damit eventuell wichtigen System-Programmen den Speicher wegnehmen?


Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,
Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!
Klar, läuft bei mir. Und?
Dann weißt Du ja das man sich nicht auf Idle-Zeiten verlassen kann. Das einzigste wovon man ausgehen kann ist dass das OS sicherstellen muss das alle Programme einen fairen Zugang zur Ressource "CPU-Zeit" bekommen.

Wenn das Betriebssystem sich eine gewisse CPU-Zeit zur Selbstverwaltung genehmigt, sehe ich darin kein Problem.
Ich sehe da schon ein Problem. Natürlich muss man dem OS-Kernel für die Aufgaben die man von ihm erledigt haben will auch angemessene Zeit zugestehen, aber das er von sich aus anfängt die CPU zu benutzen finde ich persönlich falsch.


Trotzdem sollte man schon einigermaßen dafür sorgen, dem System nicht mehr Last aufzubrummen als notwendig.
Natürlich, das tun die Programmierer ja auch. Es ist meine Aufgabe als Plattform-Designer dafür zu sorgen das diese Bemühungen auch auf meiner Plattform zum Erfolg führen. Dinge wie ein kleiner Cache-Foot-Print u.ä.m. führen auch bei mir zum Ziel.

Gegenbeispiel dafür ist Java. Hauptsache langsam. Oder Flash.
Ich hab selber schon Java-Applets programmiert und bin durchaus der Meinung das ich für den Komfort das mein Applet überall läuft ein klein wenig Performance opfern kann (so groß ist dieser Performance-Verlust bei Java nun wirklich nicht). Eine Wetter-Simulation wird man bestimmt nicht in Java programmieren, wenn jemand extra dafür etliche Millionen Euro für die Hardware ausgibt wird er auch jedes MIPS bis aufs letzte ausquetschen wollen. Ich hab mal gehört das man manchmal auf solchen Super-Computern gar kein richtiges OS hat, einfach damit möglichst wenige CPU-Takte für was anderes als die eigentliche Aufgabe drauf gehen.


Wenn die verwendeten Segmentnummern zum Task gehören, braucht das Programm sich im Regelfall nicht um FAR-Pointer scheren (ein Datenzugriff geht nach .data, ein Codezugriff nach .code usw). Problematisch wird es erst, wenn du Segmentregister umladen musst - also FAR-Pointer dereferenzierst - denn das lässt sich nicht vermeiden.
Klingt das überhaupt logisch?...
Äh, nein, das klingt für mich nicht besonders logisch. :-D
Da der Compiler nicht im voraus weiß ob der Pointer, den eine Funktion als Parameter bekommt, auf .const oder .data oder einem der Heap-Segmente oder ganz wo anders hin zeigt kann er auch nicht sagen das der Speicher-Zugriff über eines der vordefinierten Segment-Register geht. Deshalb will ich ja 16 Segment-Register haben, von denen etwa 10 zur freien Verfügung stehen (also nicht vordefiniert sind), um dem Compiler möglichst viele Freiheiten zu lassen.


Naja, ich würde mit dem Paging gezielt die Probleme der Segmentierung abschwächen/entfernen wollen.
Ganz genau darum geht es, aber dafür ist IMHO ordentliches Paging erforderlich. Im Gegensatz zum Paging auf anderen Plattformen ist mein Paging über alle CPUs synchron, es gibt also nur ein Paging-Directory das von allen CPUs benutzt wird, ich hab daher kein TLB-Trashing beim Task-Wechsel. Das ist aber auch der Grund warum ich nie Linux, oder irgend ein anderes Flat-Memory-OS, portieren kann. Es wäre aber auch totaler Unsinn ein ungeeignetes OS zu portieren.

Wenn das Konzept sich dann als nicht tragbar erweist, kann man sich immernoch um was anderes bemühen - oder Segmentierung endgültig als Mist abstempeln.
Bis jetzt sehe ich keine Probleme in meinem Konzept, ich denke es wird gut funktionieren.


Grüße
Erik

PS.: Ich hab die einzelnen Diskussionspunkte etwas defragmentiert.

PPS.: Falls ich oben ein bisschen den Oberlehrer in mir gezeigt hab bitte nicht übel nehmen. Ich hab irgendwie den unbestimmten Eindruck das wir in ein paar Details an einander vorbei reden und da wollte ich noch mal richtig erklären.
Titel: Re: Eigene CPU
Beitrag von: Svenska am 01. April 2010, 16:27
Hallo,

PPS.: Falls ich oben ein bisschen den Oberlehrer in mir gezeigt hab bitte nicht übel nehmen. Ich hab irgendwie den unbestimmten Eindruck das wir in ein paar Details an einander vorbei reden und da wollte ich noch mal richtig erklären.
Äh, ja. Nix Oberlehrer. Danke, das war richtig so.

Jetzt kann ich einiges besser nachvollziehen und verstehe teilweise auch den Gedankengang dahinter.

Also hast du den physischen Speicher in einem 1:1 Mapping in der Page-Tabelle (und das ganze so gebaut, dass damit genau kein Performance-Verlust auftritt) und dahinter virtuellen Speicher, der durch die MMU geroutet wird.
Somit ist also Paging mehr oder weniger ständig aktiv, wird aber designtechnisch möglichst umgangen.

Das einzige, was ich noch sehe, ist der physisch nutzbare Adressraum von nur 1 GB RAM (statt 4 GB) - in der 32-Bit-Variante - aber so viel möchtest du da wohl eh nicht anbasteln.

Ich bin mal gespannt, was dabei rauskommt. Gut klingen tut es.

Gruß,
Svenska
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 01. April 2010, 19:55
Hallo,


Also hast du den physischen Speicher in einem 1:1 Mapping in der Page-Tabelle (und das ganze so gebaut, dass damit genau kein Performance-Verlust auftritt)
Jain, es gibt in dem 1:1-Bereich gar kein Paging. Ich will dafür extra Range-Register vorsehen die der Paging-Unit nur einen bestimmten Bereich erlauben, alles andere geht immer dran vorbei und ist im Paging-Directory nicht präsent.

und dahinter virtuellen Speicher, der durch die MMU geroutet wird.
Genau. Nur Segmente, deren lineare Basis-Adresse nicht im 1:1-Bereich sondern im virtuellen Bereich liegt, sind vom Paging betroffen und können dann auch aus mehreren Teilen bestehen.

Somit ist also Paging mehr oder weniger ständig aktiv, wird aber designtechnisch möglichst umgangen.
Ja, das Paging ist immer aktiv aber es liegen meistens keine Segmente in dem Bereich wo Paging auch wirkt so das es meistens gar nicht benutzt wird.

Das einzige, was ich noch sehe, ist der physisch nutzbare Adressraum von nur 1 GB RAM (statt 4 GB) - in der 32-Bit-Variante - aber so viel möchtest du da wohl eh nicht anbasteln.
Dieses Problem ist mir auch schon aufgefallen. Irgendeinen Bereich im linearen Speicher brauche ich um dort hin die Segmente virtuell "auszulagern" damit sie noch in einem Stück sichtbar bleiben (das geht im 1:1-Bereich mit fragmentierten Segmenten ja eben nicht). Dieser Bereich sollte möglichst deutlich größer als der physisch vorhandene Speicher sein damit ich dort nicht so schnell in Bedrängnis gerate, schließlich gibt es auch dort externe Fragmentierung wenn ich dort immer Segmente rein mappe und dann wieder raus nehme. Andererseits werden dort immer nur wenige Segmente drin sein (meistens wohl nur ein einziges) so das der Druck wohl doch nicht so hoch ist.
Das der lineare Speicher auf der 32Bit-Version meiner Plattform recht eng ist, im Hinblick auf den machbaren physischen Speicher, ist mir klar. Genau deshalb ist mir der Pfad hin zu 64 Bit so wichtig. Wenn ich davon aus gehe das der virtuelle Speicher doppelt so groß wie der physische Speicher sein sollte (und noch etwas Memory-Mapped-I/O dazu muss) dann wird es dort erst bei 2^62 Bytes physischen Speicher eng und bis da hin ist noch ein ganz klein wenig Zeit.

Als Alternative kann ich immer noch das Paging im ganzen linearen Speicher aktivieren und das Paging-Enable-Bit in den Segment-Descriptoren nutzen (so wie ich mir das ursprünglich vorgestellt hatte bevor ich mir nähere Gedanken zum defragmentieren machte) so das immer nur die Segmente die fragmentiert sind das Paging nutzen. Trotzdem benötige ich einen Bereich im linearen Speicher in den die Segmente virtuell hin können (um an einem Stück sichtbar zu bleiben) wenn sie fragmentiert sind und sich so nicht mehr ohne Paging nutzen lassen.

Es bleibt also noch einiges zu grübeln bevor ich auch nur in die Nähe von "fertig" komme. 8-)

Ich bin mal gespannt, was dabei rauskommt.
Ja, gespannt bin ich auch. ;-)

Gut klingen tut es.
Das sollte es auch langsam, nach dem ich nun schon 4 Jahre drüber brüte. :-D


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: kevin am 01. April 2010, 20:14
Ich habe ja das nicht alles mitgelesen, dafür ist es mir zu viel. Aber vielleicht ist es ja trotzdem nicht völlig vorbei:

Jain, es gibt in dem 1:1-Bereich gar kein Paging. Ich will dafür extra Range-Register vorsehen die der Paging-Unit nur einen bestimmten Bereich erlauben, alles andere geht immer dran vorbei und ist im Paging-Directory nicht präsent.
[...]
Nur Segmente, deren lineare Basis-Adresse nicht im 1:1-Bereich sondern im virtuellen Bereich liegt, sind vom Paging betroffen und können dann auch aus mehreren Teilen bestehen.
Warum nimmst du nicht ein Flag für die Segmente her, das aussagt, ob Paging für dieses Segment aktiviert ist? Das erscheint mir irgendwie eleganter als irgendwelche magischen Speicherbereiche zu haben.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 01. April 2010, 22:22
Hallo,


Warum nimmst du nicht ein Flag für die Segmente her, das aussagt, ob Paging für dieses Segment aktiviert ist? Das erscheint mir irgendwie eleganter als irgendwelche magischen Speicherbereiche zu haben.
Das hab ich doch auch ein paar Sätze später geschrieben.

Das löst aber nicht das Problem das ich die Segmente im linearen Speicher immer am Stück brauche und wenn es im physischen Speicher keinen geeigneten Frei-Bereich gibt (wo ich das Segment kurzerhand einblenden könnte und wo es dann im Hintergrund auch physisch hin defragmentiert/verschoben wird), dann muss das Segment erst mal wo anders hin "ausgelagert" werden (nur dort ist dann das Paging nützlich, weshalb es etwas Sinn macht es auch nur dort aktiv zu haben).

Mir ist da noch die Idee gekommen als "Virtual-Swap-Out-Arrea" den Memory-Mapped-I/O-Bereich zu nehmen. Dann würden in diesem Bereich des linearen Speichers 2 Sorten an Segmenten existieren. Solche mit gesetztem Paging-Enable-Bit die per Paging in (eins oder) mehrere Teilstücke des normalen physischen Speichers zeigen und andere mit gelöschtem Paging-Enable-Bit die am Paging vorbei auf die Peripherie-Speicher zeigen. Diese Idee klingt aber selbst für meine Verhältnisse ziemlich verrückt. :-D

Prinzipiell bin ich schon daran interessiert auf die Paging-Range-Register zu verzichten und ich hätte auch gerne mehr physischen Speicher in meiner Memory-Map (jedenfalls mehr als 1/4), die Frage ist nur wie ich das angehe.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: kevin am 01. April 2010, 23:11
Das hab ich doch auch ein paar Sätze später geschrieben.
Wie gesagt, ihr schreibt mir zu viel, um alles zu lesen. ;)

Zitat
Das löst aber nicht das Problem das ich die Segmente im linearen Speicher immer am Stück brauche und wenn es im physischen Speicher keinen geeigneten Frei-Bereich gibt (wo ich das Segment kurzerhand einblenden könnte und wo es dann im Hintergrund auch physisch hin defragmentiert/verschoben wird), dann muss das Segment erst mal wo anders hin "ausgelagert" werden (nur dort ist dann das Paging nützlich, weshalb es etwas Sinn macht es auch nur dort aktiv zu haben).
Wenn du mit dem Defragmentieren anfängst, ist das Segment doch am Stück. Warum es nicht einfach dort lassen, wo es ist, während Paging aktiv ist? Oder wenn dir der Quellbereich nciht gefällt, nimmst du eben den Zielbereich her - den brauchst du genauso in zusammenhängender Form, wenn du Paging irgendwann wieder ausschalten willst.

Wenn es keinen passenden Bereich gibt, hast du doch sowieso verloren, oder?
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 02. April 2010, 12:08
Hallo,


Wie gesagt, ihr schreibt mir zu viel, um alles zu lesen. ;)
Ich hätte erwartet das Du wenigstens den Beitrag liest den Du zitierst.
[.....]
Der war doch nun wirklich nicht mehr soooooooo lang. :wink:


Wenn du mit dem Defragmentieren anfängst, ist das Segment doch am Stück.
Nicht immer, das hängt von der Ursache des Defragmentierens ab. Falls ein Segment vergrößert werden soll aber nicht einfach wachsen kann oder falls ein neues Segment angelegt werden soll und es keinen ausreichenden Frei-Speicher in einem Stück gibt dann muss das Segment sofort (sonst würde das System blockiert) in mehreren Teilen entstehen. Dazu benötige ich aber eben auch sofort einen neuen Bereich im linearen Speicher (der gerade beim zweiten Fall) nicht unbedingt im 1:1-Bereich liegt. Daher ist mir die Idee mit der virtuellen "Swap-Out-Arrea" gekommen. Wo die liegt ist erst mal egal und komplett 4 GByte RAM geht auf der 32Bit-Plattform eh nicht.

Warum es nicht einfach dort lassen, wo es ist, während Paging aktiv ist?
Das wird wohl nur selten gehen. Das geht IMHO nur wenn ich nicht-fragmentierte Segmente nur verschieben möchte (wegen memory-compaction bzw. free-space-consolidation). Obwohl auch dazu der Ziel-Bereich im linearen Speicher ja bereits frei sein muss oder nur durch das betroffene Segment selber belegt sein darf und ich daher auch das virtuelle verschieben vor dem physischen Verschieben durchführen kann.

Oder wenn dir der Quellbereich nciht gefällt, nimmst du eben den Zielbereich her
Das dürfte schon eher was bringen. Wenn ich ein Segment vergrößern muss und es nicht wachsen kann (weil was anderes im Weg liegt) dann kann ich auch gleich einen neuen Bereich für das ganze Segment suchen und dort das Segment gleich virtuell hin verlegen. Der neue Segmen-Teil wird gleich 1:1 genommen (trotz Paging) und der alte Teil wird erst mal über das Paging eingeblendet. Nach dem der Paging-Teil in die neue Heimat umgezogen ist kann dann das Paging-Enable-Flag im Segment-Descriptor einfach aus geschaltet werden.

den brauchst du genauso in zusammenhängender Form, wenn du Paging irgendwann wieder ausschalten willst.
Ja, es ist mein Ziel das nach dem defragmentieren jedes Segment an einem Stück im physischen Speicher liegt. Sonst werde ich ja das böse Paging nicht los.

Wenn es keinen passenden Bereich gibt, hast du doch sowieso verloren, oder?
Ja, genau das ist mein Problem.
Ich könnte ja theoretisch den linearen Speicher doppelt benutzen, es wäre durchaus möglich das an der selben linearen Stelle 2 verschiedene Segmente liegen, einmal mit und einmal ohne Paging, die dann auf verschiedene physische Speicher-Bereiche verweisen. Aber wie gesagt, das wäre selbst für meine Verhältnisse ziemlich verrückt (und dann wohl doch der Weg in die Hölle).

Auch hier wollte jemand einen ähnlichen Weg gehen http://www.halfbakery.com/idea/Hardware_20concurrent_20memory-compaction_20assist, zusätzliche Aufmerksamkeit verdienen die Kommentare. :wink:
Zumindest bin ich nicht der einzigste auf diesem Erdenrund der solche Gedanken hat.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: chris12 am 07. April 2010, 13:12
so da ihr hier viel diskutiert habt, wollte ich mal anfragen, ob schon was bei rausgekommen ist, also ob ihr schon ein konzept habt, oder ob ihr euch immernoch mit den ganzen einzelhein rumschlagen müsst?

ich hab schon was zu stande gebracht: ist zwar nur 8 bit aber immerhin
(http://img64.imageshack.us/img64/3170/8bitcpu.jpg)
auf dem bild hat die CPU ein byte (0Fh) aus speicherstelle 04h ausgelesen, und es dann auf dem Outport ausgegeben

so sieht die cpu in diesem zustand aus
(http://img380.imageshack.us/img380/3159/cpuu.jpg)
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 07. April 2010, 15:22
Hallo,


so da ihr hier viel diskutiert habt, wollte ich mal anfragen, ob schon was bei rausgekommen ist, also ob ihr schon ein konzept habt, oder ob ihr euch immernoch mit den ganzen einzelhein rumschlagen müsst?
Wir haben vor allem viel über Speicher-Defragmentierung zur Laufzeit diskutiert. Dieses Konzept ist jetzt etwas klarer in meinem Kopf, aber es sind noch ein paar Fragen offen.
Ansonsten grüble ich noch über viele Detailprobleme. Vor allem mein Assembler geht derzeit nicht so gut vorwärts.

ich hab schon was zu stande gebracht:
Na das ist doch schon was! :-)
Bis ich mal so weit bin wird sicher noch einiges an Zeit vergehen. :-(

so sieht die cpu in diesem zustand aus
[Bild]
Aha, ich bin, äh, verwirrt. :wink:
Mit welchem Programm ist das erstellt?


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: chris12 am 07. April 2010, 15:34
das programm nennt sich logisim http://ozark.hendrix.edu/~burch/logisim/
damit kann man diese schönen bildchen erstellen und auch gleich simulieren, ob das auch funktioniert
Titel: Re: Eigene CPU
Beitrag von: MasterLee am 09. April 2010, 22:37
Ich hatte mir das mit der Speicherbereinigung mal wie folgt vorgestellt.
Der Speicher ist in Chunks aufgeteilt. Die MMU kann über denn Typ oder einen length Parameter ermitteln wie lang jeder Chunk ist.
Nun gibt es eine Reihe von Register die alle Adressen im Speicher enthalten.
RegisterBeschreibung
pFreeAnfang des freien Bereichs
pCurrentAnfang aktueller Chunk
pNextAnfang nächster Chunk
TypTyp aktueller Chunk
SizeLänge aktueller Chunk
pReadLeseadresse
pWriteSchreibadresse
Am Anfang stehen alle Register auf Null
Nun wird wie folgt vorgegangen
Wenn ich jetzt Daten aus dem Speicher brauche muss ich denn Chunk ermitteln prüfen ob der Chunk pCurrent ist wenn ja muss ich die Adresse falls sie unter pRead liegt die Differenz zwischen pRead und pWrite verringeren.
Die Daten Chunks können über einen RADIX-Tree gefunden werden.
Der Die Zeiger im Tree müssen natürlich aktualisiert werden wenn ein Chunk bewegt wird.

Das zu implementieren wird nicht so leicht ;(
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 10. April 2010, 09:03
Hallo,


das programm nennt sich logisim http://ozark.hendrix.edu/~burch/logisim/ damit kann man diese schönen bildchen erstellen und auch gleich simulieren, ob das auch funktioniert
Danke, nur leider kann es wohl keine VHDL/Verilog-Komponenten einbinden. Eine große CPU nur durch gezeichnete Gatter zu bauen finde ich zu anstrengend. :-D


Ich hatte mir das mit der Speicherbereinigung mal wie folgt vorgestellt.  .......
Ich bin mir zwar nicht sicher ob ich verstanden hab was Du meinst aber es klingt nach Paging mit variablen Page-Größen.
Mein Ziel ist es durch Segmentierung auf Paging (meistens) verzichten zu können um damit Performance und Sicherheit zu gewinnen. Paging will ich nur dann anschalten wenn ich damit irgendwelche Speicher-Magie, wie defragmentieren u.ä., machen muss.

Das zu implementieren wird nicht so leicht ;(
Das dürfte auch ziemlich langsam werden, aber ich lass mich da auch gerne vom Gegenteil überzeugen.
Die Pages in einer Art Baum zu verwalten dürfte zu sehr vielen Baum-Ebenen führen. Mehr als 4 oder 5 Ebenen (und entsprechend viele Speicherzugriffe) sind deutlich zu ineffizient, schließlich soll damit ja RAM verwaltet werden. Vielleicht kann da die flexible Page-Größe etwas retten aber ich vermute das Dein Konzept bei normalen User-Space-Programmen auf mindestens 10 Baum-Ebenen kommt und wenn der Baum unausgeglichen wird (weil Du z.B. immer an einer Stelle neue Pages anfügst) dann werden es sicher deutlich mehr Ebenen. So ein Baum dürfte wohl auch mehr Speicher beanspruchen als klassische Page-Directorys, und bestimmt auch mehr Verwaltungsaufwand im OS-Kernel.
Für einen brauchbaren/schnellen TLB dürfen die Pages auch nur 2er-Potenzen als Größe haben. Oder sind Deine 7 Register als eine Art TLB zu verstehen? Wenn ja dann müssten die mehrmals vorhanden sein.
Beliebige 2er-Potenzen als Page-Größen (auf der 32Bit-Plattform von 4 kByte bis 128 MByte, eventuell auch nur jede zweite 2er-Potenz von 4 kByte bis 64 MByte um den TLB-L2-Cache möglichst einfach zu halten) möchte ich auch verwirklichen und dabei trotzdem die klassische Page-Directory-Struktur verwenden, ist auch kein Problem.

Falls ich Dich total missverstanden habe dann versuche es bitte noch mal genauer zu erklären.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: chris12 am 10. April 2010, 10:49
dann nimm doch http://inst.eecs.berkeley.edu/~cs61c/sp06/projs/04/logisim-ucb-2.jar da kann man "Software Gates" benutzen, die kann man in c coden, man braucht nur gcc auf dem computer installiert haben. als template kannst du http://inst.eecs.berkeley.edu/~cs61c/sp06/projs/04/template.c nemen
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 11. April 2010, 14:29
Hallo,


dann nimm doch http://inst.eecs.berkeley.edu/~cs61c/sp06/projs/04/logisim-ucb-2.jar da kann man "Software Gates" benutzen, die kann man in c coden
Also in einer "normalen" sequenziellen Programmiersprache kann man IMHO keine parallele Logik beschreiben. Für ein paar Kleinigkeiten wird sowas sicher schon mal gehen, aber für ein richtiges Logik-Design, das man auch in einem FPGA (oder gar in einem ASIC) laufen lassen will, muss man dann doch die passenden Werkzeuge benutzen.

Der Einstieg in VHDL, oder auch Verilog, ist gar nicht so extrem schwer und passende SW mit richtigem Simulator gibt es bei den FPGA-Herstellen gratis (wenn man bereit ist ein paar GBytes zu downloaden).


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: chris12 am 25. November 2010, 22:31
So jetzt möchte ich mich mal wieder melden und das thema aus der versenkung holen.
Ich hab ein wenig weiter gearbeitet und dabei sind gleich 3 cpu raus gekommen.
die drei cpu bauen alle auf einem ähnlichem ISA. der einzige unterschied zwischen allen ist, dass die erste, die 1408, eine 8bit CPU ist, die zweite, 1416, eine 16bit und die letzte eine 32bit cpu ist (ratet wie ihre bezeichnung ist ;) )
Zu allen dreien gibt es nur EINEN assembler! sie alle haben eine identischen syntax und so sind nur die ausgespuckten bitweiten unterschiedlich.
zum aufwand: der dataparser war am komplexesten, dafür aber auch am kleinsten.
anzahl der dateien: 9
 parser.cs (parst mathematische ausdrücke; 326 Zeilen)
 1408.cs (generiert code für die 1408; 487)
 1416.cs (513)
 1432.cs (511)
 bin code.cs (sollte binärcode erstellen und in eine datei schreiben, unfertig; 17)
 emu code.cs (erstellt ein emulator ram image, es gibt einen 140A emu, der teilkompatibel zur 1408 ist; 19)
 logisim code.cs (erstellt code für logisim, der dann einfach in ein ram modul gelesen werden kann; 19)
 preprocessor.cs (der preprozessor, auf den ich besonders stolz bin, er kann macros definieren, konstanten, definitions abfragen machen, nachrichten, warnungen und errors ausgeben, die der benutzer definiert und andere dateien einbinden; 317)
 Program.cs (und schließlich die hauptdatei, sie enthält alles übrige, globale variablen, userinterface und den dataparser, 514)
gesamtzahl der zeilen: alles in allem nur 2723 zeilen

und damit ihr auch seht, wie denn der qellcode dann aussieht hier noch ein kleines beispiel
%ifndef 1416
%error "Runs only on the 1416! simply Define the key '1416'."
%endif
%ifdef 1416
%warning "Make sure you assemble for the 1416!"
%define $zero r15
%define si r14
%define outr r13
%define $one r12
ld outr, 0x8000
ld $zero, 0
ld $one, 1

ld si, string
l:
mov r0, outr
sm si, $+2
lm r1, 0
cmp r1, $zero
je end
add r0, r1
out r0
out $zero
add si, $one
j l
end:
hlt

string: db "Hallo Welt!", 0
%endif
das der code gibt auf der 1416 "Hallo Welt!" aus, wenn der bildschirm sachgerecht an den outport angeschlossen ist.
achja zum hinweis:
ld -> LoaD nach(reg), was(konst)
lm -> LoadMemory nach(reg), von(konst)
sm -> StoreMemory von(reg), nach(konst)
out -> Output von(reg) auf dem 16bit outport
j -> vgl. jmp
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 26. November 2010, 18:16
Moin

Die wievielte CPU ist das jetzt ;) ? 6te?

Hast du dir schonmal gedanken über eiene komplett eigene Plattform gemacht? also Paging Interrupts und so weiter? Wäre mal interessant zu hören, wie du so etwas lösen würdest.

PNoob
Titel: Re:Eigene CPU
Beitrag von: chris12 am 26. November 2010, 18:25
das mit der sechsten CPU, dürfte hinhauen. leider funktionieren nur die letzten 3 nach meiner Zufriedenheit ^^
Das mit der eigenen Plattform, ist gar keine so verkehrte Idee, Buscontroller, Interruptcontroller, etc. könnte man ja mit dem bestand aufbauen, aber ob da Logisim noch mit macht? ^^
naja, ich werd mal sehen was ich machen werde, nen Buscontroller bräuchte ich eh.
leider muss sehr viel Code bei der Laufzeit verändert werden und ob das eine so gute Grundlage ist, weiß ich nicht. Aber man könnte ja eine CPU auf einer CPU errichten, des dürfte einige Probleme lösen, aber ob das ganze dann noch hinhaut. Naja ich werd mal sehen was die Zeit bringt.
bis dahin
mfg
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 26. November 2010, 18:30
Aber denk dir bitte nicht so was verrücktes wie Segmentierung aus. bleib beim Flat Memory Modell. Ich hab vorhin mich wieder mit dem x86 Real Mode auseinandergesetzt. Grausam.

PNoob
Titel: Re:Eigene CPU
Beitrag von: chris12 am 26. November 2010, 18:38
mal sehen, was man mit 16bit alles machen kann ^^
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 26. November 2010, 18:47
warum denn 16? ich dachte du hast ne 32 Bit CPU am laufen. nimm doch die oder will die nicht so wie sie soll?

PNoob
Titel: Re:Eigene CPU
Beitrag von: chris12 am 26. November 2010, 18:55
die 16bit cpu ist ganz einfach die, mit der ich am liebsten arbeite. bisher war sie, die am flüssigsten ausgeführt wurde, kann sich aber auch geändert haben. leider ist sie auch die, die am neusten ist, an ihr habe ich immer wieder kleine updates durchgeführt, die bei den anderen fehlen.
zu der 32 bit cpu: sie kann leider der ram nur mit einem 24 bit adressbus ansprechen, da logisim meint: "Neh du brauchst nicht mehr als 16.777.215 32bit zellen speicher ...."
Titel: Re:Eigene CPU
Beitrag von: XanClic am 26. November 2010, 22:55
Aber denk dir bitte nicht so was verrücktes wie Segmentierung aus. bleib beim Flat Memory Modell. Ich hab vorhin mich wieder mit dem x86 Real Mode auseinandergesetzt. Grausam.
Hm? Erstens würde dir Erik da was husten, auch wenn der Realmode nicht unbedingt viel mit der ihm vorschwebenden Segmentierung (die wohl eher dem Protected Mode entspricht) zu tun hat.
Zweitens: Was ist denn genau das Schlimme an der RM-Segmentierung? Man kann lustige Sachen damit machen... :wink:
Titel: Re:Eigene CPU
Beitrag von: kevin am 26. November 2010, 23:33
Mit PM-Segmentierung kann man aber noch viel lustigere Sachen machen. ;)
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 11:04
Hallo,


@chris12:
Cool, ich bin beeindruckt!
gesamtzahl der zeilen: alles in allem nur 2723 zeilen
Das klingt nach ziemlich wenig, ich bin bereits bei über 6000 Zeilen und hab gerade mal die wichtigsten Befehle eingelesen, von Binärcode ist bei mir noch nichts zu sehen. Ich vermute aber mal das Du einen einfachen Single-Pass-Assembler gebaut hast. Bei meiner CPU-Architektur würde ich nicht mal mit nem Two-Pass-Assembler hinkommen.

Schon allein mein Literal-Parser hat über 1500 Zeilen, dafür kann er aber auch Klammern und Vorrangregeln (so wie C) und ist ziemlich einfach um neue Operatoren zu erweitern.
Und ich benutze C++ aber nur wenig von der STL so das ich viel von Hand mache was bei C# wohl mit der fest eingebauten Library zu erledigen ist.

Zur Simulation würde ich eher ModelSim empfehlen auch wenn damit riesige simulierte Speichermengen in einem noch viel größerem RAM-Verbrauch enden, um einen 4MBit-SRAM zu simulieren hat das ModelSim fast 500MB Speicher gefressen (aber da gibt es Möglichkeiten und Wege das zu minimieren). Der entscheidende Punkt ist aber das Du dafür VHDL (oder Verilog) lernen musst, überlege es Dir mal.


Aber denk dir bitte nicht so was verrücktes wie Segmentierung aus.
Was ist daran Bitte "verrückt"? Überlege doch erst mal was man mit richtiger dynamischer Segmentierung alles machen kann. Du kannst z.B. Speicher sharen und es ist völlig egal ob das 3 Byte oder 3 TByte sind (unabhängig ob ausgerichtet oder nicht), das macht im Kernel nicht einen CPU-Takt unterschied. Mit Segmentierung kannst Du den Speicher zur Laufzeit defragmentieren und noch ganz andere tolle Sachen anstellen. Und Du verlierst nicht bei jedem Speicherzugriff Performance, so wie beim Paging. Ließ Dir mal diesen Thread komplett durch.

Ich hab vorhin mich wieder mit dem x86 Real Mode auseinandergesetzt. Grausam.
Die Segmentierung im x86-Real-Mode ist auch grausam, da ist nichts dynamisches oder zumindest flexibles dran. Das ist einfach nur ein dämlicher Weg um einer 16Bit-CPU 20Bit-Adressen zu ermöglichen.

bleib beim Flat Memory Modell.
Und damit auf der dunklen Seite? Klar mit Flat-Memory kommt man leichter zum Ziel, aber ist nicht auch der Weg das Ziel?


Mit PM-Segmentierung kann man aber noch viel lustigere Sachen machen.
Das ist die richtige Einstellung! Willst Du nicht bei mir einsteigen? Ein Paar fähige Hände könnte ich gut gebrauchen und als Gegenleistung darfst Du mit einem signifikant erweiterten Horizont rechnen. ;)


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 27. November 2010, 11:19
Entschuldigung, das ich deine Segmentierung nicht gut finde. Vielleicht ist deine Segmentierung besser. aber ich kenne sie nur ein wenig Theoretisch. Praktisch habe ich damit noch nichts angestellt. Ich kenne Nur die X86 RealMode Segmentierung und finde die Schei**.
Ich hoffe das deine Art der Segmentierung besser ist und wünsche dir viel Glück bei deiner CPU.
Ich bleibe mit meiner Architektur ganz brav beim FlatMemory  Modell, weil ich Flat Memory mit Paging persönlich schöner finde.

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 11:37
Hallo,


Entschuldigung, das ich deine Segmentierung nicht gut finde.
Dafür brauchst Du Dich nicht zu entschuldigen, mit Meinungen hab ich kein Problem. Ich hab ein Problem damit das Du ein Konzept das Du nicht kennst als "verrückt" abstempelst ohne das rational zu begründen.

Ich kenne Nur die X86 RealMode Segmentierung
Und genau da ist das Problem.

und finde die Schei**.
Dem kann ich uneingeschränkt zustimmen.

weil ich Flat Memory mit Paging persönlich schöner finde.
Nie und nimmer würde ich Dir das ausreden, ich zeige Dir höchstens die belegbaren Nachteile Deiner Entscheidung.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 27. November 2010, 12:05
Moin

Entschuldigung, das ich deine Segmentierung nicht gut finde.
Dafür brauchst Du Dich nicht zu entschuldigen, mit Meinungen hab ich kein Problem. Ich hab ein Problem damit das Du ein Konzept das Du nicht kennst als "verrückt" abstempelst ohne das rational zu begründen.
Ich habe deine Threads bisher alle so ziemlich komplett durchgelesen. aber ich glaube ich habe dein Konzept nicht so ganz verstanden. Ich sehe da irgendwie keinen Vorteil gegenüber der x86 Realmode Segmentierung.
auch verstehe ich nicht, wie du die Segmente verwalten willst, also welche Segmente u welchem Task gehören.

PNoob
Titel: Re:Eigene CPU
Beitrag von: kevin am 27. November 2010, 12:44
Mit PM-Segmentierung kann man aber noch viel lustigere Sachen machen.
Das ist die richtige Einstellung! Willst Du nicht bei mir einsteigen? Ein Paar fähige Hände könnte ich gut gebrauchen und als Gegenleistung darfst Du mit einem signifikant erweiterten Horizont rechnen. ;)
Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Titel: Re:Eigene CPU
Beitrag von: MasterLee am 27. November 2010, 13:29
zu der 32 bit cpu: sie kann leider der ram nur mit einem 24 bit adressbus ansprechen, da logisim meint: "Neh du brauchst nicht mehr als 16.777.215 32bit zellen speicher ...."
Nimm einfach mehrere RAM Bausteine ein Verteiler und einen Demuxer denn du dann mit denn sel Anschluss des RAM verbindest.
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 14:06
Hallo,


aber ich glaube ich habe dein Konzept nicht so ganz verstanden.
Dann habe ich meine Ideen wohl zu kompliziert beschrieben, das tut mir leid.

Ich sehe da irgendwie keinen Vorteil gegenüber der x86 Realmode Segmentierung.
Die x86-Real-Mode-Segmentierung ist starr, da gibt es keine Flexibilität: wo das Segment im Speicher liegt ist durch den numerischen Wert des Selector bereits festgelegt und auch die Größe ist fix. Bei dynamischen Segmenten kannst du für jedes Segment beliebig definieren wo es im linearen Speicher liegt und wie groß es ist. Man kann z.B. für einen Stack ein Segment mit 16 kBytes als Startgröße nehmen und wenn der Thread doch mehr Stack benötigt dann kann man das Segment einfach vergrößern und wenn das Vergrößern an der aktuellen Stelle im linearen Speicher nicht geht dann kann man das Segment auch kurzerhand wo anders hin legen (und dann später in aller Ruhe im Hintergrund den linearen Speicher defragmentieren) ohne das der betreffende Thread das überhaupt bemerkt. Und da ja jeder Prozess seine eigene LDT hat kommt da auch nichts durcheinander und es gibt damit einen starken Speicherschutz obwohl alle Segmente von allen Prozessen im selben linearen Speicher liegen. Das geht alles nur mit dynamischen und flexiblen Segmenten. All das kann der 386 im PM prinzipiell auch, wenn auch manches davon nicht so elegant wie meine CPU das können soll (nebst dessen das seine nur 6 Segmentregister einfach ein Performance-Engpass darstellen).

auch verstehe ich nicht, wie du die Segmente verwalten willst, also welche Segmente u welchem Task gehören.
Jeder Prozess hat seine eigene LDT und in der stehen alle Segmente des betreffenden Prozess drin. Also ein Execute-Only-Segment mit dem Code, ein Write-Only-Segment für .const ein Read-Write-Segment mit .data und .bss (im Executable muss natürlich nur .data drin sein) und für jeden Thread noch ein Stack-Segment und ein TLS-Segment (bis auf die Stack-Segmente wird die Größe all dieser Segmente zur Laufzeit nicht mehr geändert). Dazu kommen noch die Segmente aus denen die Heap-Verwaltung eben den Heap baut (die Größe dieser Segmente wird natürlich zur Laufzeit dem Bedarf entsprechend angepasst).

Wenn Du konkrete Fragen hast dann stell sie einfach, wir sind ja hier im richtigen Thread dafür.


Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 27. November 2010, 15:07
aber ich glaube ich habe dein Konzept nicht so ganz verstanden.
Dann habe ich meine Ideen wohl zu kompliziert beschrieben, das tut mir leid.
Eine eigene Archtitektur ist zu kompliziert, die kann man schlecht beschriben. nicht umsonst sind die Intel Manuals so groß. Es ist nicht dein Fehler.
auch verstehe ich nicht, wie du die Segmente verwalten willst, also welche Segmente u welchem Task gehören.
Jeder Prozess hat seine eigene LDT und in der stehen alle Segmente des betreffenden Prozess drin. Also ein Execute-Only-Segment mit dem Code, ein Write-Only-Segment für .const ein Read-Write-Segment mit .data und .bss (im Executable muss natürlich nur .data drin sein) und für jeden Thread noch ein Stack-Segment und ein TLS-Segment (bis auf die Stack-Segmente wird die Größe all dieser Segmente zur Laufzeit nicht mehr geändert). Dazu kommen noch die Segmente aus denen die Heap-Verwaltung eben den Heap baut (die Größe dieser Segmente wird natürlich zur Laufzeit dem Bedarf entsprechend angepasst).
Was ist eine LDT?
Wenn Du konkrete Fragen hast dann stell sie einfach, wir sind ja hier im richtigen Thread dafür.
Ich werde mir nacher alles nochmal durchlesen und dann auch fragen stellen.

Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)
Erik deine Architektur ist einfach zu anders. und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.

PNoob
Titel: Re:Eigene CPU
Beitrag von: MNemo am 27. November 2010, 15:44
Erik deine Architektur ist einfach zu anders. und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.

Naja, der RM hat bei mir da keinen Einfluss drauf, wenn man diese Form der Adressraumerweiterung überhaupt Segmentierung nennen darf. Prinzipiell sind Segmente auch was tolles.<edit> Ich sehe nur alle Vorteile durch die Verwaltung von Segmenten wieder zu Nichte gemacht.Ich halt nur den Aufwand die Segmente zu Verwalten für größer als ihren Nutzen.</edit> Man muss den RAM ständig defragmentieren.
Dazu sehe ich nur die zwei Möglichkeiten:
- Paging, womit das ganze transparent ablaufen kann
- oder, das gesamte Segment blockieren bis es fertig verschoben ist.

Bei ersterem, gibt es keinen Geschwindigkeitsvorteil gegen über Paging, weil man ja Paging benutzt.
(Hier bin ich ein wenig verwirrt, weil ich es irgend wie so mitkommen habe, dass Eriks Wahl ist.*)
Letzteres wird vermutlich schwere negative Auswirkungen auf die Performance haben.

Und wenn ich auf ein Flat-memory-model zurückgreife, hab ich zwar keine Abgrenzung mehr zwischen Code, Daten und Stack. Aber ich kann jetzt defragmentieren wenn es gerade günstig ist. Und wenn man dann auf 4MB oder sogar 1GB Pages 'hoch schalten' kann müssten sich die TLB'misses beim Pagelookup auch drastisch reduzieren.

*Aber ich hab seine Romane auch nicht komplett gelesen.
Titel: Re:Eigene CPU
Beitrag von: kevin am 27. November 2010, 16:19
Wenn ich Erik richtig verstanden habe, will er Paging nur einschalten, solange er swappt/defragmentiert und hinterher wieder ausschalten. Was natürlich ein ganzer Haufen Aufwand ist, nur um diese eine Operation durchzuführen.

ein Write-Only-Segment für .const
Sicher? ;)

Zitat
Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)
Naja, im Ernst: Das tolle am x86 ist, dass ich ihn mehrfach vorrätig habe und damit ohne weiteres rumspielen kann. Dass du mehr oder weniger im luftleeren Raum jahrelang rumplanst und bastelst, bevor man an irgendwelche echten Ergebnisse auch nur denken kann, nötigt mir Respekt ab, aber ich kann das nicht. Ich brauche was zum rumspielen.

Was ist eine LDT?
Local Descriptor Table, also sowas wie die GDT, nur hat jeder Prozess eine eigene davon. Lies dir mal das Intel-Manual durch, der Protected Mode hat viele interessante Sachen. Wenn du den PM einigermaßen verstanden hast, wirst du Eriks Architektur auch besser nachvollziehen können.
Titel: Re:Eigene CPU
Beitrag von: chris12 am 27. November 2010, 17:11
zu der 32 bit cpu: sie kann leider der ram nur mit einem 24 bit adressbus ansprechen, da logisim meint: "Neh du brauchst nicht mehr als 16.777.215 32bit zellen speicher ...."
Nimm einfach mehrere RAM Bausteine ein Verteiler und einen Demuxer denn du dann mit denn sel Anschluss des RAM verbindest.
kann ich machen, nur müsste ich dann den source code bzw das binäre programm entsprechend aufteilen und dazu hatte ich noch keine lust sowas einzubauen ^^
aber eine gute idee, danke
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 17:55
Hallo,


Erik deine Architektur ist einfach zu anders.
Das soll sie ja auch sein. ;)

und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.
Das ist wohl eher das Problem. Intel hat mit seinem vermurksten Real-Mode die Leute eher abgeschreckt. Im PM blendet man die Segmentierung für gewöhnlich einfach aus und kommt daher quasi gar nicht damit in Berührung. Ich hab im 386-PM schon einiges mit Segmentierung machen dürfen, z.B. im Rahmen von verschiedenen DOS-Extendern, und hab dabei auch die Vorzüge kennen gelernt.


Ich sehe nur alle Vorteile durch die Verwaltung von Segmenten wieder zu Nichte gemacht. Man muss den RAM ständig defragmentieren.
Wieso sollte die Verwaltung von Segmenten kompliziert sein? Das sind auch nur zusammenhängende Bereiche im linearen Adressraum, das ist IMHO nicht komplexer als ein HEAP der auch zusammenhänge Bereiche unterschiedlicher Größe in seinem einen virtuellen Adressraum (der ja auch linear ist) verwaltet. Defragmentieren muss man auch nicht "ständig" sondern nur wenn es nötig ist, bei einem kaum belasteten System sollte das entsprechend selten sein.

Dazu sehe ich nur die zwei Möglichkeiten:
- Paging, womit das ganze transparent ablaufen kann
- oder, das gesamte Segment blockieren bis es fertig verschoben ist.
Exakt. Ich habe mich für Paging entschieden weil die zweite Option wohl wirklich nicht in frage kommt. Damit mir das Paging nicht die Performance (von der ich ja immer schwärme) kaputt macht wird es nur zum defragmentieren/swappen eingeschaltet und auch nur für die Segmente die davon betroffen sind, alle anderen Segmente bleiben davon verschont.

Und wenn man dann auf 4MB oder sogar 1GB Pages 'hoch schalten' kann müssten sich die TLB'misses beim Pagelookup auch drastisch reduzieren.
Genau das kann man aber nur selten machen, das geht für einen großen Batzen an Code oder für die .data-Sektion aber z.B. für Stacks ist das nur wenig praktikabel denn wenn Du im virtuellen Adressraum eine große Page anbietest musst Du auch den physischen Speicher dafür haben (und zwar passend ausgerichtet). Da der virtuelle Adressraum typischer Flat-Memory-Programme oft nur dünn belegt ist kommen die großen Pages eben kaum zum Einsatz.

*Aber ich hab seine Romane auch nicht komplett gelesen.
Daraus macht Dir wirklich niemand einen Vorwurf. ;)
Manche meiner Beiträge sind schon wirklich extrem lang, aber Segmentierung ist eben nichts alltägliches und da muss man manche Kleinigkeiten schon etwas ausführlicher erklären.


Was natürlich ein ganzer Haufen Aufwand ist, nur um diese eine Operation durchzuführen.
Wo ist das ein Aufwand? Ich muss doch nur ein Mapping einrichten und dafür ist der Aufwand erstmal nur direkt abhängig von der Größe des betreffenden Speicherblocks, das ist auf Flat-Memory-Systemen doch ganz genau so. Das Paging ist immer da und wird dann nur für das eine Segment eingeschaltet, ich hab da ein extra Bit im Segment-Descriptor vorgesehen. Irgendwo in diesem Thread hab ich mal erklärt wie ich mir das vorstelle was alles passieren muss wenn ein Segment vergrößert werden soll aber etwas im Weg ist, klar ist da erst mal ein gewisser Aufwand aber dann läuft das eigentliche Defragmentieren bequem und unbemerkt im Hintergrund ab.

ein Write-Only-Segment für .const
Sicher? ;)
Mist, das muss natürlich Read-Only heißen. Aber es wird bei mir auch Write-Only-Segmente geben z.B. für IPC wenn der Service in einen Bereich nur Daten rein legen soll.

Das tolle am x86 ist, dass ich ihn mehrfach vorrätig habe und damit ohne weiteres rumspielen kann.
Okay, das ist natürlich ein gutes Argument.

Dass du mehr oder weniger im luftleeren Raum jahrelang rumplanst und bastelst, bevor man an irgendwelche echten Ergebnisse auch nur denken kann, nötigt mir Respekt ab
Naja, das erste mal lief bereits vor gut 2 ein wenig händisch assemblierter Code in einem noch extrem primitiven Simulator, die OpCodes händisch zusammen zu bauen war alles andere als angenehm. Genau deswegen will ich ja jetzt endlich einen Assembler, ich denke wenn der zuverlässig Code ausspuckt geht der Rest wieder deutlich zügiger voran. Nur sollte ich mir bis da hin mit meinen OpCodes und Descriptor-Tabellen recht sicher sein ansonsten muss ich an vielen Stellen Code anfassen.

Lies dir mal das Intel-Manual durch, der Protected Mode hat viele interessante Sachen. Wenn du den PM einigermaßen verstanden hast, wirst du Eriks Architektur auch besser nachvollziehen können.
Ja, das stimmt, ich habe vom 386-PM viele Ideen übernommen (eine gewisse Ähnlichkeit ist nicht abzustreiten). Ich denke auch das ein Großteil meiner Ideen für das OS auf dem 386 ebenfalls funktionieren können. Der größte Unterschied ist das ich das Paging pro Segment einschalten kann und das es vor allem nur eines gibt (also alle Prozesse im selben linearen Adressraum existieren), ansonsten existiert noch der Unterschied das ich von vornherein 64Bit mit integriert habe (als AMD den Long-Mode erschaffen hat wurden ja all die tollen Sachen raus geworfen).


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: MNemo am 27. November 2010, 20:52
Ich sehe nur alle Vorteile durch die Verwaltung von Segmenten wieder zu Nichte gemacht. Man muss den RAM ständig defragmentieren.
Wieso sollte die Verwaltung von Segmenten kompliziert sein? Das sind auch nur zusammenhängende Bereiche im linearen Adressraum, das ist IMHO nicht komplexer als ein HEAP der auch zusammenhänge Bereiche unterschiedlicher Größe in seinem einen virtuellen Adressraum (der ja auch linear ist) verwaltet. Defragmentieren muss man auch nicht "ständig" sondern nur wenn es nötig ist, bei einem kaum belasteten System sollte das entsprechend selten sein.
Solange man nicht defragmentieren muss, ist alles ganz easy. Nur gehe ich davon aus, dass das mit dem Defragmentieren nicht ganz so selten sein wird, und vor allem immer dann wenn es gerade auf Performance ankommt. Also wenn man z. B. gerade ein 'make -j5 bzimage' im Hintergrund laufen lässt, könnte ich mir vorstellen, dass der RAM  ziemlich zerhackt wird. Es sei den bis du fertig bist hat jemand nen GCC-Daemon gebaut, der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)

Und wenn man dann auf 4MB oder sogar 1GB Pages 'hoch schalten' kann müssten sich die TLB'misses beim Pagelookup auch drastisch reduzieren.
Genau das kann man aber nur selten machen, das geht für einen großen Batzen an Code oder für die .data-Sektion aber z.B. für Stacks ist das nur wenig praktikabel denn wenn Du im virtuellen Adressraum eine große Page anbietest musst Du auch den physischen Speicher dafür haben (und zwar passend ausgerichtet). Da der virtuelle Adressraum typischer Flat-Memory-Programme oft nur dünn belegt ist kommen die großen Pages eben kaum zum Einsatz.
Code und Datenbereich sind das worauf es ankommt. Der Stack wird sich nur selten in einem Bereich größer 4KB bewegen, und so sowieso nur eine TLB'miss auslösen. Und das mit der passende Ausrichtung lässt sich ja machen.

*Aber ich hab seine Romane auch nicht komplett gelesen.
Daraus macht Dir wirklich niemand einen Vorwurf. ;)
Da bin ich aber beruhigt. :wink:
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 21:23
Hallo,


der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)
Warum sollten den "tausende" Segmente für ein paar Compiler-Instanzen nötig sein?

Ansonsten hast Du natürlich recht das fragmentierte Segmente gerade dann entstehen wenn Performance benötigt wird. Aber das Problem trifft doch Flat-Memory-Systeme genau so. Speicher wird üblicherweise dann angefordert wenn er auch benötigt wird und dann muss das OS dem so schnell als möglich nachkommen. In einem Flat-Memory-System musst Du ein Mapping anlegen (und dafür im Zweifelsfall ne Menge einzelner physischer Pages aus dem wahrscheinlich ziemlich zerklüfteten physischen Speicher zusammensuchen), das kann man kurzsichtig etwas beschleunigen indem nur der virtuelle Adressraum belegt wird und das eigentliche Mapping dann später über unzählige und teure (daher kurzsichtig) Page-Faults nachholt. Wenn ich ein Segment anlegen muss kann ich prüfen ob das sofort in einem Stück geht (wäre der Optimalfall) oder ob es fragmentiert entstehen muss und dann muss ich ebenso ein Mapping anlegen und hab dabei auch die selben kurzsichtigen Beschleunigungsoptionen. Damit der Optimalfall möglichst oft eintritt kann ich im Hintergrund prophylaktisch defragmentieren aber das lohnt sich IMHO nur wenn der lineare Speicher auch bereits nennenswert ausgelastet oder stark zerklüftet ist (und das ist wohl nur selten).

Code und Datenbereich sind das worauf es ankommt.
Und was ist mit dem Heap, der ist in klassischen Flat-Memory-System auch oft ziemlich zerklüftet.

Der Stack wird sich aber nur selten in einem bereich größer 4KB bewegen.
Das würde ich nicht unbedingt so sehen. Klar wissen viele Programmierer um die Stackknappheit aber es gibt trotzdem viele Situationen wo man mal tiefe Call-Trees erzeugt oder einfach mal einen dicken Batzen Daten als lokale Variablen anlegt. Der Punkt ist das man mit Flat-Memory diese Dynamik nur unzureichend abfangen kann: entweder man macht die Stacks zu klein um mehr Threads zu ermöglichen und riskiert damit eben ein Out-of-Stack oder man macht die Stacks größer und begrenzt dadurch die maximale Anzahl möglicher Threads. Ein Dilemma das eben daraus entsteht das der virtuelle Adressraum in einem Flat-Memory-Prozess nur genau ein mal existiert, das dieses Dilemma keine spürbaren Auswirkungen hat liegt vor allem daran das die meisten Programmierer mit Stack und Threads sparsam umgehen.

Und das mit der passende Ausrichtung lässt sich ja machen.
Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: MNemo am 27. November 2010, 22:07
Hallo,
der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)
Warum sollten den "tausende" Segmente für ein paar Compiler-Instanzen nötig sein?
Ich denke der Linux-Kernel besteht aus mehr als nur ein paar Dateien. Und ein ich weiß zwar nicht genau welche Prozesse alle beim Kompilieren einer Datei gestartet werden, aber des sind AFAIK mehr als einer. Die beim Parsen der Datei dann feststellen, dass noch zusätzlich ein paar header Dateien nachgeladen werden müssen, so dass das Daten Segment wachsen muss, was zu Problemen führt wenn der darauf folgende Adressraum an eine der Parallel laufenden Compiler Instanzen vergeben wurde.

Ansonsten hast Du natürlich recht das fragmentierte Segmente gerade dann entstehen wenn Performance benötigt wird. Aber das Problem trifft doch Flat-Memory-Systeme genau so. Speicher wird üblicherweise dann angefordert wenn er auch benötigt wird und dann muss das OS dem so schnell als möglich nachkommen. In einem Flat-Memory-System musst Du ein Mapping anlegen (und dafür im Zweifelsfall ne Menge einzelner physischer Pages aus dem wahrscheinlich ziemlich zerklüfteten physischen Speicher zusammensuchen), das kann man kurzsichtig etwas beschleunigen indem nur der virtuelle Adressraum belegt wird und das eigentliche Mapping dann später über unzählige und teure (daher kurzsichtig) Page-Faults nachholt. Wenn ich ein Segment anlegen muss kann ich prüfen ob das sofort in einem Stück geht (wäre der Optimalfall) oder ob es fragmentiert entstehen muss und dann muss ich ebenso ein Mapping anlegen und hab dabei auch die selben kurzsichtigen Beschleunigungsoptionen. Damit der Optimalfall möglichst oft eintritt kann ich im Hintergrund prophylaktisch defragmentieren aber das lohnt sich IMHO nur wenn der lineare Speicher auch bereits nennenswert ausgelastet oder stark zerklüftet ist (und das ist wohl nur selten).
Eine freie Page finde ich mit niedrigem konstanten Aufwand. Und ein Mapping anzulegen ist gegen das Defragmentieren trivial.

Code und Datenbereich sind das worauf es ankommt.
Und was ist mit dem Heap, der ist in klassischen Flat-Memory-System auch oft ziemlich zerklüftet.
Was willst du mir damit sagen?
Den Heap kann und braucht man auch nicht Defragmentieren. Und wenige Byte große Löscher im Heap stören den Einsatz von großen Pages auch nicht.

[…] aber es gibt trotzdem viele Situationen wo man mal tiefe Call-Trees erzeugt […]
Z. B. ein Backtrack-Algorithmus mit hoher Rekursion tiefe? Da wird nur relativ selten zwischen unterster und oberster Ebene wechseln. Von daher ist der Stackbereich auf dem auch gearbeitet wird immer noch nicht so groß.

Und das mit der passende Ausrichtung lässt sich ja machen.
Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
[edit]Das dachte ich zumindest mal gelesen zu haben. Aber ich finde es gerade nicht. Vielleicht hab ich das auch mit KSM(Kernel Samepage Merging) verwechselt.[/edit]
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 27. November 2010, 22:46
Hallo,


was zu Problemen führt wenn der darauf folgende Adressraum an eine der Parallel laufenden Compiler Instanzen vergeben wurde.
Ja, dann muss das Segment fragmentiert werden. Aber wo genau ist das Problem? Ein passendes Mapping für den neuen Speicher muss doch ein Flat-Memory-System auch machen. Ich sehe da erstmal keinen relevanten Performance-Unterschied, nur das ich mit den Segmenten prinzipiell die Möglichkeit hab das Paging wieder los zu werden. Das mag in einem Szenario wo mit recht kurzlebigen Prozessen nur so geschmissen wird, kein Vorteil sein (weil es einfach nicht dazu kommt) aber bei längerlebigen Prozessen, die nicht ständig große Mengen an Speicher allozieren oder freigeben, bringt das IMHO schon einen signifikanten Vorteil.

Eine freie Page finde ich mit niedrigem konstanten Aufwand. Und ein Mapping anzulegen ist gegen das Defragmentieren trivial.
Das erste trifft auf mein System doch auch zu und beim zweiten muss ich sagen das mein System im Worst-Case nicht schlechter ist als ein Flat-Memory-System aber im Best-Case eben deutlich schneller.

Den Heap kann und braucht man auch nicht Defragmentieren. Und wenige Byte große Löscher im Heap stören den Einsatz von großen Pages auch nicht.
Das man den Heap nicht defragmentieren kann ist mir klar (schließlich gibt es ja Pointer die da hin zeigen) aber es können durchaus große Löcher im Heap entstehen deren Pages dann manchmal frei gegeben werden um eben weniger Speicher zu verbrauchen und diese Löcher können dann dem Einsatz großer Pages schon mal im Weg sein.

.... Von daher ist der Stackbereich auf dem auch gearbeitet wird immer noch nicht so groß.
Stimmt.

Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
Echt, das wusste ich noch nicht. Dann unterscheidet sich von den grundlegenden Fähigkeiten her mein OS ja gar nicht so sehr von Linux außer eben in dem Punkt das wenn mein OS diese Fähigkeiten anwendet zum Schluss das Paging abgeschaltet werden kann.

Bei was für einer Gelegenheit defragmentiert den Linux? Gibt es da einen Idle-Thread im Kernel?


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: MNemo am 27. November 2010, 23:07
Ok. Das du letzt endlich doch Paging verwendest hab ich jetzt nicht beachtet. :oops:
Das man den Heap nicht defragmentieren kann ist mir klar (schließlich gibt es ja Pointer die da hin zeigen) aber es können durchaus große Löcher im Heap entstehen deren Pages dann manchmal frei gegeben werden um eben weniger Speicher zu verbrauchen und diese Löcher können dann dem Einsatz großer Pages schon mal im Weg sein.
Ich gehe halt von weniger Sozialen Anwendungen aus, die siech nicht die mühe machen löscher im Heap frei zu geben.

Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
Echt, das wusste ich noch nicht. Dann unterscheidet sich von den grundlegenden Fähigkeiten her mein OS ja gar nicht so sehr von Linux außer eben in dem Punkt das wenn mein OS diese Fähigkeiten anwendet zum Schluss das Paging abgeschaltet werden kann.

Bei was für einer Gelegenheit defragmentiert den Linux? Gibt es da einen Idle-Thread im Kernel?
Siehe hier mein Edit. Scheinbar hab ich mich da geirrt.
Titel: Re:Eigene CPU
Beitrag von: kevin am 27. November 2010, 23:13
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
[edit]Das dachte ich zumindest mal gelesen zu haben. Aber ich finde es gerade nicht. Vielleicht hab ich das auch mit KSM(Kernel Samepage Merging) verwechselt.[/edit]
Hm, eventuell im Zusammenhang mit Transparent Huge Pages?
Titel: Re:Eigene CPU
Beitrag von: MNemo am 27. November 2010, 23:28
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
[edit]Das dachte ich zumindest mal gelesen zu haben. Aber ich finde es gerade nicht. Vielleicht hab ich das auch mit KSM(Kernel Samepage Merging) verwechselt.[/edit]
Hm, eventuell im Zusammenhang mit Transparent Huge Pages?
Ja, das entspricht zumindest dem was ich im Hinterkopf hatte.
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 28. November 2010, 10:59
Hallo,


Ok. Das du letzt endlich doch Paging verwendest hab ich jetzt nicht beachtet.
Ich muss, ansonsten müsste ich das Verschieben/Defragmentieren von Segmenten als atomare Aktion ausführen und dafür eventuell auch noch große Teile des System lahm legen. Das Paging ist ein essentieller Bestandteil meines Konzepts, eben weil ich damit ne Menge Speichermagie machen kann und weil ich es auch zum Swappen benötige.

Ich gehe halt von weniger Sozialen Anwendungen aus, die siech nicht die mühe machen löscher im Heap frei zu geben.
Das würde aber bedeuten das Du weniger soziale Programme auch noch mit zusätzlicher Performance belohnst. Das klingt irgendwie sehr nach der realen Welt da draußen. ;)
In meinem OS soll das anders sein. Da werden Programme die riesige aber nur dünn benutze Segmente haben bei Speicherknappheit bestraft in dem die unbenutzten Bereiche ausgelagert werden und dafür bei diesem Segment das Paging aktiv sein muss. Ein Segment das nicht vollständig im Speicher ist ist also auch immer fragmentiert.

Siehe hier mein Edit. Scheinbar hab ich mich da geirrt.
Dein Edit kam wohl kurz nachdem ich auf den "Zitieren"-Button geklickt hatte, schade dass das Forum da nicht warnt so wie bei neuen Beiträgen.
Aber ohne das Defragmentieren von physischen Pages kann ein Flat-Memory-System eigentlich kaum große Pages anbieten, also ist es IMHO logisch das Linux (und wohl auch Windows) das kann.


Zitat
aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Mir ist dieser Satz noch mal durch den Kopf gegangen und ich muss sagen das er ein Widerspruch in sich selbst ist. Wie kann man etwas (als krank) beurteilen das man nicht "einmal annähernd durchgespielt" hat?
Da wird doch das eigentliche Problem offensichtlich: die Leute haben eine vorgefasste Meinung (die basiert zumeist auf Gerüchten und ein paar unangenehmen Erinnerungen mit etwas völlig anderem aus grauer Vorzeit das zufällig den selben Namen hat) und fertig.
Sorry, wenn ich da jetzt jemanden auf den Schlips getreten bin aber das musste ich mal loswerden.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: kevin am 28. November 2010, 11:11
Zitat
aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)
Mir ist dieser Satz noch mal durch den Kopf gegangen und ich muss sagen das er ein Widerspruch in sich selbst ist.
Musst du eigentlich immer jedes Wort auf die Goldwaage legen? Das ist mit der Zeit anstrengend. ;)

Zitat
Wie kann man etwas (als krank) beurteilen das man nicht "einmal annähernd durchgespielt" hat?
Da wird doch das eigentliche Problem offensichtlich: die Leute haben eine vorgefasste Meinung (die basiert zumeist auf Gerüchten und ein paar unangenehmen Erinnerungen mit etwas völlig anderem aus grauer Vorzeit das zufällig den selben Namen hat) und fertig.
Dass man etwas noch nicht in die Praxis umgesetzt ("durchgespielt") hat, heißt doch nicht, dass man automatisch keine Ahnung davon hat? Ja, mir fehlt vielleicht die praktische Erfahrung, aber deswegen ist meine Meinung trotzdem nicht auf Gerüchten und unangenehmen Erinnerungen, sondern auf der offiziellen Dokumentation des Prozessors basiert.

Wenn du deine hohen Ansprüche behalten willst, solltest du ab sofort nicht mehr über deine Plattform reden (oder hast du mittlerweile eine Maschine physisch vor dir stehen?) und jede Diskussion mit Svenska sofort beenden (wenn ich mich nicht täusche, gehört der hier nämlich zur Fraktion der reiner Theoretiker). Spekulationen über die Kaputtheit von etwas, das du selbst noch nicht implementiert hast, gehören dann selbstverständlich auch der Vergangenheit an.

Stimmst du mir zu, dass deine Aussage Blödsinn war?
Titel: Re:Eigene CPU
Beitrag von: Svenska am 28. November 2010, 12:23
und jede Diskussion mit Svenska sofort beenden (wenn ich mich nicht täusche, gehört der hier nämlich zur Fraktion der reiner Theoretiker).
Korrekt. :-P Schlimm?
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 28. November 2010, 19:30
Hallo,


Musst du eigentlich immer jedes Wort auf die Goldwaage legen? Das ist mit der Zeit anstrengend. ;)
Naja, da ist so eine meiner Schwächen. Eigentlich war das auch nicht konkret an Dich gerichtet, ich hab beim Zitat extra absichtlich das Referer weg gelassen. Es ging mir um die generelle Einstellung vieler Leute zur Segmentierung die oft durchscheint.

Dass man etwas noch nicht in die Praxis umgesetzt ("durchgespielt") hat, heißt doch nicht, dass man automatisch keine Ahnung davon hat?
Das hab ich auch nicht behauptet, es geht mir um die Abwertung (als krank) die nicht rational begründet wurde.

Ja, mir fehlt vielleicht die praktische Erfahrung, aber deswegen ist meine Meinung trotzdem nicht auf Gerüchten und unangenehmen Erinnerungen, sondern auf der offiziellen Dokumentation des Prozessors basiert.
Das ist auch völlig richtig, aber Du hast da IMHO keine Meinung sondern eine (negative) Wertung ausgedrückt (sorry, da ist wieder die Goldwaage).

Wenn du deine hohen Ansprüche behalten willst
Was für Ansprüche eigentlich? Das ich es nicht mag wenn die Segmentierung so unbegründet abgewertet wird?
Wenn dann auch noch im selben Satz erklärt wird das diese Wertung keinerlei Grundlage hat empfinde ich persönlich das als unpassend.

solltest du ab sofort nicht mehr über deine Plattform reden
Ist das Dein Wunsch?

Fraktion der reiner Theoretiker
Was ist so schlimm an Theoretikern? Ohne die hätte heute keiner von uns einen Computer vor der Nase.

Spekulationen über die Kaputtheit von etwas, das du selbst noch nicht implementiert hast
Aber ich hab Segmentierung und auch Paging bereits mehrfach ausgiebig benutzt. Hat hier schon mal jemand selber Paging implementiert? Muss man dann auch mal ein Auto implementiert haben um sich überhaupt über das Fahrverhalten eines existierenden Autos beschweren zu dürfen?

Stimmst du mir zu, dass deine Aussage Blödsinn war?
Nein, meine Aussage war vielleicht nicht perfekt formuliert und sicher auch etwas frustgeladen aber ich stehe dazu! Wenn ich was negatives über Paging gesagt hab dann hab ich das auch immer rational begründet und nicht einfach inhaltslose Attribute wie "krank" oder "kaputt" benutzt.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: XanClic am 28. November 2010, 20:03
es geht mir um die Abwertung (als krank) die nicht rational begründet wurde.
Hm? Darf ich dich an deine Aussage bezüglich der Real-Mode-„Segmentierung“ erinnern?

und finde die Schei**.
Dem kann ich uneingeschränkt zustimmen.


Ich persönlich hätte mir da schon eine Erklärung gewünscht. :-)
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 28. November 2010, 20:26
Hallo,


Ich persönlich hätte mir da schon eine Erklärung gewünscht. :-)
dann schau mal 2 Beiträge davor:
Die Segmentierung im x86-Real-Mode ist auch grausam, da ist nichts dynamisches oder zumindest flexibles dran.
oder 4 Beiträge dahinter:
Die x86-Real-Mode-Segmentierung ist starr, da gibt es keine Flexibilität: ....

Wenn ich mich unklar/unvollständig ausgedrückt haben sollte schreibe ich dazu gerne noch ein paar detailliertere/weitere konkrete Punkte.


Sorry, aber so aus dem Kontext gerissen, ist auch nicht der richtige Weg. Meine Beiträge sind üblicherweise eh schon viel zu lang, da kann ich nicht auch noch jedes mal jeden Kram wiederholen.
Bezüglich der dynamischen Segmentierung des 386-PM hab ich hier in diesem Forum noch nicht ein einziges Argument gelesen!


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 28. November 2010, 22:32
Moin

Irgendwie schweifen wir hier ziemlich vom Topic ab. Lasst uns die Segmentation Diskussion in einem extra Thread diskutieren.

PNoob
Titel: Re:Eigene CPU
Beitrag von: XanClic am 28. November 2010, 23:11
Erik: Ich fand das nur komisch, weil du auch geschrieben hattest (wimre), dass die RM-Segmentierung lediglich ein merkwürdiger Weg zum Erhalten von einem MB Speicher mit 16-Bit-Adressen sei. Meiner Meinung nach ist das der einzige Zweck, und damit ist jede Dynamik nicht Sinn der Sache – inwiefern der Weg aber merkwürdig ist, erschloss sich mir nicht ganz. :-)
(Die Aussage von PNoob („Die Segmentierung ist Schei**“) klang für mich auch eher so wie „Wer hat sich das denn ausgedacht“ und nicht wie „Wieso kann die nicht so viel wie die x86-Segmentierung“)

PNoob: Da das hier angefangen wurde, schreib ich auch einfach hier rein und überlass den Rest frech den Mods. :-D
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 29. November 2010, 00:03
Also da ja nun inzwischen alle mitbekommen haben dürften, das ich auch eine eigene Architektur entwickle, schreibe ich jetzt auch mal was dazu.

Den Namen, DPSKA (Dem PNoob Seine Kaputte Architektur), hat sich taljeth ausgedacht. Vieleh Dank nochmals dafür. Ich wäre nimals auf nen anständigen Namen gekommen.

Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halte und zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen. Außerdem vereinfacht der 32 Bit adressbus breite das Speichermanagment. Als Speichermodell kommt bei mir FlatMemory zum einsatz. Der Speicherschutz wird mit Paging realisiert. Der Befehlssatz wird ein 3 Operanden Befehlssatz werden. Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab. SMP mit bis zu 32 CPU's und 64 Kernen pro CPU ist geplant. Es gibt 256 IRQ lines, somit sollte es für jede Situation genügend freie IRQ lines geben, so das keine Geräte sich eine Line teilen müssen. Zur Systemsicherheit, gibt es 2 Privelege Level. In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.

Das war mal ein grobüberblick über meine Architektur. Wenn ihr Fragen habt, dann fragt.

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 29. November 2010, 10:35
Hallo,


Irgendwie schweifen wir hier ziemlich vom Topic ab. Lasst uns die Segmentation Diskussion in einem extra Thread diskutieren.
Das fände ich auch gut, meinetwegen ab meinem Beitrag von Sonntag Vormittag.


Ich fand das nur komisch, weil du auch geschrieben hattest (wimre), dass die RM-Segmentierung lediglich ein merkwürdiger Weg zum Erhalten von einem MB Speicher mit 16-Bit-Adressen sei.
Ja, dieser Meinung bin ich (nebst dessen das sich das eigentlich nicht unbedingt Segmentierung nennen sollte aber egal). Eines der Probleme mit dem x86-Real-Mode ist das der Befehlssatz einem quasi dazu zwingt sehr häufig die Segment-Register neu zu laden (wenn das Programm mehr als 64kB benötigt), es sind nicht nur die RM-Segmente starr und unflexibel sondern auch der Befehlssatz (z.B. MOVS). Bei Flat-Memory (und im Prinzip ist ein RM-Programm das komplett in 64kB passt ja auch flach) ist das keine Einschränkung aber die Segmentierung wird bei x86 nur recht lieblos unterstützt (in allen Modi). Ich persönlich bin der Meinung das eine größere Verarbeitungsbreite der CPU, z.B. 32 Bit, oder flexiblere Befehle der bessere Weg wären aber das kostet beides auch mehr Transistoren auf dem Chip. Ich kann verstehen warum Intel damals diesen Weg gegangen ist aber toll find ich (als Programmierer) ihn trotzdem nicht (auch wenn diesem Weg vielleicht der 286-PM und damit auch der 386-PM zu verdanken ist).


Den Namen, DPSKA (Dem PNoob Seine Kaputte Architektur), hat sich taljeth ausgedacht. Vieleh Dank nochmals dafür.
Und genau da ist wieder dieses bestimmte Attribut "Kaputt". Wieso wird eine Idee bereits als kaputt bezeichnet (bewertet!) bevor überhaupt etwas konkretes bei raus gekommen ist? Hätte nicht erst mal DPSA gereicht?
PNoob: Bedanken solltest Du Dich für diesen Namen IMHO nicht.


Ich würde mich sehr freuen wenn diese spezielle Diskussion (um Meinungen und Wertungen) auf einem etwas sachlicherem Niveau weitergeführt werden würde. Es tut mir sehr Leid das ich mich Sonntag Vormittag nicht beherrschen konnte und mal ein unangenehmes Thema angeschnitten hab.


also wieder On-Topic (und das im Off-Topic-Board ;) ) :


.... schreibe ich jetzt auch mal was dazu.
Genau dazu war dieser Thread ja auch gedacht.

Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halte
Wenn die CPU intern mit 64Bit (virtuellen) Adressen umgehen kann aber extern nur 32Bit hergibt (also so ähnlich wie ein 386SX) dann ist das keine so schlechte Idee, auch wenn die 32Bit gemessen an aktuellen PCs sicher etwas knapp kalkuliert sind. Der Hyper-Transport von AMD unterstützt wimre auch nur 40Bit physische Adressen.

zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.
Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.

Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.
Also einen extra I/O-Adressraum und nicht über MMIO? Alle anderen CPU-Architekturen gehen von den I/O-Ports zu MMIO über (falls sie überhaupt mal I/O-Ports unterstützt haben). Kann dann die Peripherie per eigenständigem Busmastering auf den normalen RAM zugreifen? Das wäre sinnvoll da dort ja die Nutzdaten der User-Mode-Prozesse liegen und um die geht es doch schließlich. Soll die Peripherie überhaupt Speicherbereiche anbieten können (sowas wie der Frame-Buffer einer Grafikkarte)?

Es gibt 256 IRQ lines ...
Wirklich als eigenständige Leitungen? Bei PCI-Express hat man darauf komplett verzichtet (an den PCI-Express-Slots gibt es keine IRQ-Pins mehr). Schau Dir mal das MSI(-X) aus der PCI-Spec an, das hat ne Menge Vorteile gegenüber dedizierten IRQ-Signalen.

In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.
Dann solltest Du aber 2 verschiedene Register für den Flags vorsehen, eines wo die normalen Flags (C,S,N,O) drin sind und eines für Flags wie IE, damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 29. November 2010, 13:54
Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halte und zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.
Du hast also 64-Bit-Register, von denen die oberen 32 Bit nicht auf den Adressbus herausgeführt werden. Das ist in Ordnung. Vielleicht solltest du es aber vorsehen, dass man da ein paar Bits noch nachträglich rausführen kann - als Erweiterung.

Wie möchtest du das Teil bauen, wenn nicht im FPGA? Ein CPLD wird dafür definitiv zu klein sein und von TTL rate ich dir ab. Wenn du so nette Features wie Paging und verschiedene CPU-Modi hast, dann steigt der Aufwand enorm, außerdem musst du bei so breiten Bussen (64 Bit innerhalb der CPU!) und hohen Taktfrequenzen extrem auf die Latenzlaufzeiten aufpassen. Wenn die Leitungen verschieden lang (um eine Ecke legen!) sind, kommen die Signale zu verschiedenen Zeiten am Ende an, was das Auslesen zu einem Zeitpunkt unmöglich macht. Du müsstest also über einen gewissen Zeitraum integrieren (Kondensator), was die maximale Taktrate enorm absenkt.

Mal abgesehen davon sind mir TTL-Bausteine nur bis 50 MHz bekannt, bis etwa 20 MHz sind sie auch bezahlbar. Das gilt für ein einzelnes Gatter. Diese Zahl teilst du durch die Anzahl der Gatter, die ein Signal von Eingang bis Ausgang durchlaufen muss und erhältst dann ungefähr die reale Geschwindigkeit. Zum Vergleich: In einem (teuren) FPGA sind für ein Gatter bis zu 1 GHz möglich.

Außerdem vereinfacht der 32 Bit adressbus breite das Speichermanagment. Als Speichermodell kommt bei mir FlatMemory zum einsatz. Der Speicherschutz wird mit Paging realisiert. Der Befehlssatz wird ein 3 Operanden Befehlssatz werden.
Also an sich nichts neues.

Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.
Machbar, aber ich finde MMIO einfach angenehmer. Mit dem I/O-Konzept verzichtest du übrigens auf DMA... es sei denn, du hinterlegst den I/O-Bereich mit Speicher. Und um dann Vorteile daraus zu kriegen, müsstest du jeden CPU-Befehl doppelt implementieren: Einmal für MEM, einmal für I/O. Das heißt zwei vollständige Adressierungsdekoder und eine Opcodeverschwendung.

Machbar, aber imho unelegant.

SMP mit bis zu 32 CPU's und 64 Kernen pro CPU ist geplant.
Du weißt schon, was du dir damit aufhalst?

Es gibt 256 IRQ lines, somit sollte es für jede Situation genügend freie IRQ lines geben, so das keine Geräte sich eine Line teilen müssen. Zur Systemsicherheit, gibt es 2 Privelege Level. In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.
Besitzt die CPU bereits 256 IRQ-Leitungen oder wirst du einen speziellen IRQ-Controller benutzen?

Baust du 1 IRQ-Leitung an, dann ist das Prinzip wie bei heute üblichen CPUs (an dieser Leitung hängt ein IRQ-Controller). Baust du 8 Leitungen, um 256 IRQs zu verwalten, dann kann immer nur ein IRQ gleichzeitig aktiv sein. Oder du baust 256 Leitungen an die CPU an, was enorm viel Lötarbeit ist...

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: MNemo am 29. November 2010, 13:58
Wieso wird eine Idee bereits als kaputt bezeichnet (bewertet!) bevor überhaupt etwas konkretes bei raus gekommen ist?
IMHO ist eine unkonkrete Architektur per Definition Kaputt, bis sie konkret und voll funktionsfähig ist.

zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.
Der Sinn und Zweck von FPGAs ist das 'Hardware Bauen' zu vermeiden. Ein Chip in Hardware zu gießen kosten ein paar Millionen $. Und eine 64-Bit CPU lötet man sich auch nicht einfach mal so zusammen. Siehe mycpu.eu, was für ein Klotz, und das (wimre) für nur 8-Bit.
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 29. November 2010, 15:00
Hallo,


Baust du 1 IRQ-Leitung an, dann ist das Prinzip wie bei heute üblichen CPUs (an dieser Leitung hängt ein IRQ-Controller). Baust du 8 Leitungen, um 256 IRQs zu verwalten, dann kann immer nur ein IRQ gleichzeitig aktiv sein. Oder du baust 256 Leitungen an die CPU an, was enorm viel Lötarbeit ist...
Oder er baut einen IRQ-BUS, sowas in der Art wie der dedizierte APIC-BUS den es mal zu PentiumPro-Zeiten gab, das ist wenig Lötarbeit und die Anzahl der verwaltbaren IRQs hängt nur vom Protokoll ab das da drauf läuft. Mit ein wenig Geschick bei der Protokoll-Entwicklung sollten auch die IRQ-Latenzen kaum darunter leiden. Oder er leitet die IRQs als Messages auf dem normalen Bus mit durch, dafür lohnt sich auch mal ein Blick in die Hyper-Transport-Spezifikation (AMD prahlt schließlich damit das der HT deutlich kleinere IRQ-Latenzen hat als PCI-Express, was auch mit Sicherheit nicht gelogen ist).


Ein Chip in Hardware zu gießen kosten ein paar Millionen $.
Wenn es nur was einfaches ist (was der Chip-Hersteller aus der Komponenten-Datenbank kurz zusammen klickt) kommt man eventuell auch für um die 500'000 $ zum fertigen Silizium. Aber da würde ich auf jeden Fall vorher die Preise und Leistungen der Chip-Gießereien (http://de.wikipedia.org/wiki/Foundry) vergleichen.
Wobei ich nicht denke das es das ist was PNoob im Sinn hatte.

Siehe mycpu.eu, was für ein Klotz, und das (wimre) für nur 8-Bit.
Ja, "nur" 8Bit, und trotzdem ist der Fuß kaputt wenn das Ding vom Tisch fällt. ;)


IMHO ist eine unkonkrete Architektur per Definition Kaputt, bis sie konkret und voll funktionsfähig ist.
Echt? Ich bin irgendwie sehr enttäuscht so etwas hier zu lesen. Die meisten Leute hier haben doch erst mal nur Ideen vorzuweisen.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 29. November 2010, 20:21
Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halte
Wenn die CPU intern mit 64Bit (virtuellen) Adressen umgehen kann aber extern nur 32Bit hergibt (also so ähnlich wie ein 386SX) dann ist das keine so schlechte Idee, auch wenn die 32Bit gemessen an aktuellen PCs sicher etwas knapp kalkuliert sind. Der Hyper-Transport von AMD unterstützt wimre auch nur 40Bit physische Adressen.
Genau so war es gedacht. Die CPU später auf echte 64 Bit aufzubohren ist dann auch kein Problem.

zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.
Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.
Darüber habe ich mir noch keine Gedanken gemacht. TTL ist glaube ich etwas zu kompliziert dafür.

Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.
Also einen extra I/O-Adressraum und nicht über MMIO? Alle anderen CPU-Architekturen gehen von den I/O-Ports zu MMIO über (falls sie überhaupt mal I/O-Ports unterstützt haben). Kann dann die Peripherie per eigenständigem Busmastering auf den normalen RAM zugreifen? Das wäre sinnvoll da dort ja die Nutzdaten der User-Mode-Prozesse liegen und um die geht es doch schließlich. Soll die Peripherie überhaupt Speicherbereiche anbieten können (sowas wie der Frame-Buffer einer Grafikkarte)?
Also Die Geräte können alle selbständig auf den Speicher zugreifen.

Es gibt 256 IRQ lines ...
Wirklich als eigenständige Leitungen? Bei PCI-Express hat man darauf komplett verzichtet (an den PCI-Express-Slots gibt es keine IRQ-Pins mehr). Schau Dir mal das MSI(-X) aus der PCI-Spec an, das hat ne Menge Vorteile gegenüber dedizierten IRQ-Signalen.
Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.

In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.
Dann solltest Du aber 2 verschiedene Register für den Flags vorsehen, eines wo die normalen Flags (C,S,N,O) drin sind und eines für Flags wie IE, damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).
OK da hast du recht.

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 30. November 2010, 12:29
Hallo,


Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.
Darüber habe ich mir noch keine Gedanken gemacht. TTL ist glaube ich etwas zu kompliziert dafür.
Darüber solltest du Dir aber mal Gedanken machen. Meiner persönlichen Meinung nach ist TTL keine echte Option (sorry Svenska), das macht extrem viel Aufwand und dürfte auch einiges an Geld verschlingen. Bei einer sehr simplen 8Bit-CPU kann TTL noch in die Kategorie "schwer aber realistisch" fallen aber für eine 64Bit CPU ist das wohl ganz offensichtlich nicht machbar.

Also Die Geräte können alle selbständig auf den Speicher zugreifen.
Dann nutze den Speicheradressraum auch für die Ressourcen welche von der HW angeboten werden, Du machst Dir damit vieles leichter.

Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.
Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll. Das hat den Vorteil das die CPU keine Tabelle dafür benötigt wo sie dann erst nachschauen muss von wo sie denn jetzt die Befehle holen soll (verringert die Latenz) und es ist der CPU völlig egal wie viele verschiedene IRQ-Handler es eigentlich gibt (das ist dann eine reine Design-Frage für den IRQ-Controller und damit flexibel ohne die CPU ändern zu müssen). Wenn Du aber ein Micro-Kernel-OS implementieren möchtest ist es auch OKay wenn es aus CPU-Sicht nur einen einzigen IRQ-Handler (im Kernel-Mode) gibt der dann die IRQ-Nummer als Parameter bekommt und damit nur noch eine asynchrone IPC-Message an den entsprechend registrierten IRQ-Handler vom User-Mode-Treiber schicken muss (diesen Weg möchte ich gehen).

.... damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).
OK da hast du recht.
Du solltest generell dafür sorgen das normale Befehle möglichst wenige Möglichkeiten haben eine Exception auszulösen, wenn Du mehrere unabhängige Befehle hast die alle garantiert keine Exception werfen kannst du die gefahrlos parallel ausführen ohne komplizierte Dinge wie Registerrenaming zu benötigen. Wenn Du das schaffst ist auch Out-of-Order-Execution nicht mehr weit und damit bekommst Du einen enormen Performance-Schub.


Für meine CPU hab ich beschlossen erst mal nur den 60Bit-Befehlssatz fertig zu stellen und die kleineren Codierungen erst dann weiter zu bearbeiten wenn ich einen funktionierenden Assembler und einen funktionierenden Simulator hab, erst dann kann ich real existierenden Code analysieren um zu schauen welche Befehle und Varianten den überhaupt häufig genug sind damit sich dafür kürzere OpCodes lohnen. Ich hab zwar bereits verschiedene kleinere Algorithmen händisch compiliert und assembliert um zu analysieren wo kritische Punkte sind aber es wäre sicher von Vorteil das noch einmal mit einer breiteren Code-Basis zu tun bevor ich endgültige Entscheidungen treffe.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 30. November 2010, 14:27
Moin

Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.
Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll. Das hat den Vorteil das die CPU keine Tabelle dafür benötigt wo sie dann erst nachschauen muss von wo sie denn jetzt die Befehle holen soll (verringert die Latenz) und es ist der CPU völlig egal wie viele verschiedene IRQ-Handler es eigentlich gibt (das ist dann eine reine Design-Frage für den IRQ-Controller und damit flexibel ohne die CPU ändern zu müssen). Wenn Du aber ein Micro-Kernel-OS implementieren möchtest ist es auch OKay wenn es aus CPU-Sicht nur einen einzigen IRQ-Handler (im Kernel-Mode) gibt der dann die IRQ-Nummer als Parameter bekommt und damit nur noch eine asynchrone IPC-Message an den entsprechend registrierten IRQ-Handler vom User-Mode-Treiber schicken muss (diesen Weg möchte ich gehen).
Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 30. November 2010, 14:49
Hallo,


Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?
Ja, genau das meinte ich mit der Idee für ein Micro-Kernel-OS. Aber wenn Du doch einen Monolithen haben willst ist diese Idee weniger gut geeignet, also kein KO-Kriterium aber kostet eben etwas Latenz. Wenn Du etwas mehr Flexibilität möchtest dann lieber die Variante wo der IRQ-Controller die Einsprungspunkte kennt und der CPU bei jedem IRQ sagt wo der zugehörige Handler ist (das kann auch immer der selbe sein). Für ein paar HW-Komponenten wirst du eventuell auch in einem Micro-Kernel-OS dedizierte Handler im Kernel-Mode haben wollen, z.B. für den HW-Timer wo der IRQ-Handler möglicherweise direkt in der Lage sein soll einen Thread auf zu wecken oder eine asynchrone IPC-Message zu versenden.

Die Interrupts für den Kontext-Switch würde ich auch mit nem eigenständigen (und reentranten wegen SMP) Handler erledigen, da ist Performance wichtig. Die Verwaltung der Zeitscheiben sollte auch nicht über den normalen Timer laufen sondern jede CPU sollte einen eigenen primitiven Counter haben der dann direkt diesen Kontext-Switch-Interrupt auslöst ohne Umweg über einen IRQ-Controller.

Dazu kommen in jedem Fall noch ein paar feste Handler für die Exceptions aber das dürfte recht überschaubar sein und ist auch nicht ganz so extrem performancekritisch. Aber der Syscall-Handler sollte schon einen eigenen Einsprungspunkt haben.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 30. November 2010, 15:37
Moin

Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?
Ja, genau das meinte ich mit der Idee für ein Micro-Kernel-OS. Aber wenn Du doch einen Monolithen haben willst ist diese Idee weniger gut geeignet, also kein KO-Kriterium aber kostet eben etwas Latenz. Wenn Du etwas mehr Flexibilität möchtest dann lieber die Variante wo der IRQ-Controller die Einsprungspunkte kennt und der CPU bei jedem IRQ sagt wo der zugehörige Handler ist (das kann auch immer der selbe sein). Für ein paar HW-Komponenten wirst du eventuell auch in einem Micro-Kernel-OS dedizierte Handler im Kernel-Mode haben wollen, z.B. für den HW-Timer wo der IRQ-Handler möglicherweise direkt in der Lage sein soll einen Thread auf zu wecken oder eine asynchrone IPC-Message zu versenden.
Das macht man doch momentan fast genauso. man legt für alle Interrupts einen stub an, der dann die eigentliche funktion aufruft. daher sollte man keinen unterschied merken.

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 30. November 2010, 16:04
Hallo,


Das macht man doch momentan fast genauso. man legt für alle Interrupts einen stub an, der dann die eigentliche funktion aufruft. daher sollte man keinen unterschied merken.
Du meinst das man auf x86 256 winzige Assembler-Stubs anlegt die dann alle auf die selbe Funktion springen welche dann per switch wieder verzweigt? Auf x86 sicher ein guter Weg aber generell finde ich das eher unelegant.

Auf meiner CPU wird es etwa 20 Einsprungspunkte geben, und jeder Handler wird ein klein wenig anders sein. Der Handler für den Kontext-Switch-Interrupt wird so schnell als möglich alle Register sichern und dann den Scheduler anspringen, wogegen der Handler für den Syscall erst mal ein switch mit der übergebenen Syscall-Nummer macht (ohne irgendetwas zu sichern) und dann in jeder Funktion individuell entschieden wird ob überhaupt irgendwelche Register zu sichern sind (get_pid wird sicher gar nichts sichern sondern einfach nur die Prozess-ID aus dem Prozess-Decriptor auslesen und sofort nen IRET machen) und wenn ja wie viele Register (bei einem Syscall der blockiert wird natürlich alles direkt in den aktuellen Thread-Descriptor gesichert bevor der Scheduler aufgerufen wird ansonsten eher nur das was tatsächlich im Weg ist auf den Kernel-Mode-Stack). Wenn man viele Register hat muss man da etwas vorsichtiger sein.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 01. December 2010, 10:35
Meiner persönlichen Meinung nach ist TTL keine echte Option (sorry Svenska), das macht extrem viel Aufwand und dürfte auch einiges an Geld verschlingen. Bei einer sehr simplen 8Bit-CPU kann TTL noch in die Kategorie "schwer aber realistisch" fallen aber für eine 64Bit CPU ist das wohl ganz offensichtlich nicht machbar.
Dem kann ich uneingeschränkt zustimmen.

Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll.
Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:

Modus 0: Der Interruptcontroller (bzw. die Hardware) legt ein Byte auf den Datenbus, welches von der CPU als Befehl betrachtet und ausgeführt wird. Für eingebettete Systeme mit fest eingebautem OS recht sinnvoll.

Modus 1: NMI und IRQ funktionieren ähnlich. Die CPU springt an Adresse 0066H (NMI) oder 0038H (IRQ). Der IRQ kann aber trotzdem maskiert werden. Für Minimalsysteme, wo es nur zwei Interruptquellen geben kann.

Modus 2: Der Interruptcontroller (bzw. die Hardware) legt ein Byte auf den Datenbus, welches die Interruptnummer darstellt (nur gerade Zahlen erlaubt). Diese Zahl gibt die unteren 8 Bit der IRQ-Handler-Adresse an, die oberen 8 Bit stehen im I-Register. Flexibel, für größere Systeme.

Eine Mischung aus Modus 1 und 2 wäre das, was Erik vorgeschlagen hat (IRQ-Nummer auf Datenbus, aber feste Sprungadresse - die Nummer taucht dann evtl. in einem Register auf). Die indirekte Variante (Modus 2 oder x86) mit einer Tabelle kostet etwas Speicher, kostet etwas Latenz, ist komplizierter zu implementieren, ist dafür aber maximal flexibel. Die schnellste Variante ist Modus 0; sinnvoll wäre z.B.: der IRQ-Controller erzeugt einen (für jeden IRQ konfigurierbaren?) Befehl, wenn ein entsprechender IRQ auftritt. Für den Timer (häufiger Einzeiler) ist das durchaus sinnvoll. Das setzt natürlich voraus, dass du in eine Befehlslänge auch eine Sprungadresse reinbekommst. Also wenn deine Befehle 32 Bit haben, dann kannst du bei 64-Bit-Datenworten deine IRQ-Handler nur in den ersten 4 GB (die restlichen 32 Datenbit) des Speichers haben. Unkonventionell, aber möglich.

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 01. December 2010, 21:47
Hallo,


Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:
Sorry, aber wir wollen uns doch nach vorne entwickeln, der Z80 war doch schon Überholt wo von uns noch keiner gelebt hat, ;)
Als nächstes kommst Du noch mit PDP-11 oder ähnlich alten Dinosauriern. Ich finde ja eher wir sollten uns an schlanken Systemen mittleren Alters orientieren: MIPS, ARM, SPARC oder PA-RISC (sortiert in absteigendem Alter und aufsteigender Komplexität). Vielleicht auch etwas deutlich Jüngeres wie der Itanium, wobei der gleich in doppelter Hinsicht kein Vorbild ist: erst mal ist der viel zu komplex und zweitens ist er genau so untergegangen wie seine namens- und seelenverwandte Titanik. Ein wirklich rundum empfehlenswertes CPU-Vorbild gibt es IMHO einfach nicht, da hilft wohl nur sich selber nen Kopf zu machen.

IRQ-Nummer auf Datenbus, aber feste Sprungadresse - die Nummer taucht dann evtl. in einem Register auf
Vielleicht sollte der IRQ-Controller trotzdem auch die Sprungadresse mit auf den Bus packen, dann hätte man maximale Flexibilität ohne damit die CPU belasten zu müssen (eine in der CPU implementierte Tabelle ist immer entweder zu groß oder zu klein, eben dafür sollte der IRQ-Controller zuständig sein). Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.

Ich hab noch mal bei meiner CPU nachgesehen: ich hab genau 8 Exception-Einsprungspunkte (die Adresse jeweils in einem globalen Register verwaltet), dazu kommen 2 verschiedene Interrupt-Einsprungspunkte (einmal für den Kontext-Switch der nicht über den IRQ-Controller geht und einmal für die normalen HW-IRQs) und dazu noch 2 Einsprungspunkte für den Syscall-Befehl (das es zwei sind hat den Grund das alle Syscall-Nummern kleiner als 1024 in einen extra Handler gehen sollen damit dieser ohne weitere Range-Checks usw. die richtige Funktion aus nem simplen Array holen kann, wegen der Performance). Alle diese Einsprungspunkte, außer der für die HW-IRQs, kommen aus 11 globalen Registern welche die CPU direkt zur Verfügung hat um eben eine möglichst kleine Latenz zu erreichen. Feste Adressen (wie bei ARM oder Z80) finde ich persönlich doof weil man dann dafür sorgen muss das an dieser Stelle auch wirklich RAM ist und weil dort eh nur ein Sprung auf den tatsächlichen Handler steht. Gerade in diesem Punkt möchte ich sehr flexibel bleiben und dem OS die Möglichkeit geben die CPU möglichst schnell direkt dort hin springen zu lassen wo dann auch wirklich der Handler liegt.

Ein weiterer wichtiger Designpunkt im Zusammenhang mit Interrupts ist die Frage ob es möglich sein soll innerhalb eines Handlers wieder neue Unterbrechungen an zu nehmen. Soll z.B. der Syscall-Handler durch externe IRQs oder durch Exceptions unterbrechbar sein? Wenn ja, dann muss die CPU in der Lage sein mehrere Kontexte ineinander verschachtelt zu sichern (eine CPU die eigenständig den Stack benutzen kann, so wie x86, ist da klar im Vorteil).

Fragen über Fragen, eine eigene Plattform zu entwickeln bringt einem eine ganze Reihe kniffliger Fragen auf den Tisch. Was soll z.B. alles passieren von dem Moment an wo das System mit elektrischer Energie versorgt wird bis zu dem Moment wo das OS dem User ein Login bietet? Soll es ein BIOS geben das die HW rudimentär in Betrieb nimmt und ein beliebiges OS von einem beliebigen Massenspeicher holt oder soll das OS tiefer im System verankert sein und alles selber machen?


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 01. December 2010, 21:51
Moin

Fragen über Fragen, eine eigene Plattform zu entwickeln bringt einem eine ganze Reihe kniffliger Fragen auf den Tisch. Was soll z.B. alles passieren von dem Moment an wo das System mit elektrischer Energie versorgt wird bis zu dem Moment wo das OS dem User ein Login bietet? Soll es ein BIOS geben das die HW rudimentär in Betrieb nimmt und ein beliebiges OS von einem beliebigen Massenspeicher holt oder soll das OS tiefer im System verankert sein und alles selber machen?
Ich habe das so geplant. wenn Strom kommt, dann wird als erstes ein BIOS geladen, welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüft,  dann wird von einem Massenspeicher ein Bootloader geladen und dem die kontrolle übergeben. was dann kommt ist Systemspezifisch.

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 01. December 2010, 22:01
Hallo,


welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüft
Das klingt alles so einfach, ich hab da sicher etliche Monate drüber gebrütet um auch nur mal ein grobes Konzept zu haben.

dann wird von einem Massenspeicher ein Bootloader geladen
Also so wie bei x86. Was denkst du was alles nötig ist um überhaupt von einem Massenspeicher einen OS-Boot-Loader laden zu können? Soll dieser OS-Boot-Loader Dein BIOS auch benutzen können (was wie die BIOS-INTs auf x86) um eben auf Massenspeicher zuzugreifen oder ein paar Buchstaben auf den Bildschirm bringen zu können?

In meine Plattform wird es kein BIOS o.ä. geben, da liegt das OS in einem Flash der durch die CPU direkt angesprochen werden kann (als ganz normaler Speicher) und davor kommt ein minimaler Initial-Loader der einfach nur das Image sucht und anspringt. Wenn das OS-Image dann startet gibt es nichts weiter außer Stack und eben den Flash mit dem Code.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 01. December 2010, 22:29
Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:
Sorry, aber wir wollen uns doch nach vorne entwickeln, der Z80 war doch schon Überholt wo von uns noch keiner gelebt hat, ;)
Ich sprach vom Augen öffnen. Ob du nun ARM, MIPS oder x86 anschaust, dann hast du überall Vektorinterrupts, die von einem Interruptcontroller auf eine IRQ-Leitung plus das Bussystem gemultiplext werden. (Bei SoCs ist der Interruptcontroller natürlich auf dem Die mit drauf.) Der Z80 hatte da vollständig andere Ideen.

Geschichte wiederholt sich. Frühe CP/M- und Unixdateisysteme konnten keine Dateien vergrößern, wie deprimierend, untauglich und überholt. Das ISO-Dateisystem von CDs und DVDs kann das aber auch nicht... man soll die historischen Ideen nicht aus dem Auge verlieren. Die Technik mag komplett überholt sein - und ob du die Vektortabelle nun einsparst, weil dir der Platz im RAM zu knapp bemessen ist oder ob dir die Latenz zu hoch ist, spielt ja keine Rolle mehr. Aber die Idee ist da.

Ein wirklich rundum empfehlenswertes CPU-Vorbild gibt es IMHO einfach nicht, da hilft wohl nur sich selber nen Kopf zu machen.

IRQ-Nummer auf Datenbus, aber feste Sprungadresse - die Nummer taucht dann evtl. in einem Register auf
Vielleicht sollte der IRQ-Controller trotzdem auch die Sprungadresse mit auf den Bus packen, dann hätte man maximale Flexibilität ohne damit die CPU belasten zu müssen (eine in der CPU implementierte Tabelle ist immer entweder zu groß oder zu klein, eben dafür sollte der IRQ-Controller zuständig sein). Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.[/quote]Wozu? Jeder sinnvoll genutzte Interrupt ist dem System bekannt, also brauchst du in Hardware nicht doppelt arbeiten, um der CPU einmal eine interruptspezifische Funktion und die Interruptnummer mitteilen zu können.

Kostet nur Silizium und im Endeffekt nutzt du genau eine von beiden Möglichkeiten sinnvoll. Maximale Flexibilität ist aber richtig.

Feste Adressen (wie bei ARM oder Z80) finde ich persönlich doof weil man dann dafür sorgen muss das an dieser Stelle auch wirklich RAM ist und weil dort eh nur ein Sprung auf den tatsächlichen Handler steht.
Bei ARM weiß ich es grad nicht, aber beim Z80 sind die Adressen nicht fest. Sie liegen nur alle zusammen in einem (kleinen) Bereich. Man muss ja die historischen Beschränkungen nicht mit dem Konzept übernehmen, außerdem sind die Grenzen heute ganz andere. Wenn IRQ-Handler in den unteren 4 GB des Speichers liegen müssen, sehe ich da kein Problem. Wenn sie in den unteren 256 Bytes liegen müssen, schon...

Was soll z.B. alles passieren von dem Moment an wo das System mit elektrischer Energie versorgt wird bis zu dem Moment wo das OS dem User ein Login bietet? Soll es ein BIOS geben das die HW rudimentär in Betrieb nimmt und ein beliebiges OS von einem beliebigen Massenspeicher holt oder soll das OS tiefer im System verankert sein und alles selber machen?
Ich habe das so geplant. wenn Strom kommt, dann wird als erstes ein BIOS geladen, welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüft,  dann wird von einem Massenspeicher ein Bootloader geladen und dem die kontrolle übergeben. was dann kommt ist Systemspezifisch.
Also das klassische Modell vom x86. Was ist an deinem Konzept eigentlich anders als der Rest? Jede Architektur zeichnet sich durch irgendetwas aus, was zeichnet deine aus?

Eriks Architektur geht ganz neuartig mit Speicher um, meine Architektur ist (theoretisch) in TTL realisierbar.

In meine Plattform wird es kein BIOS o.ä. geben, da liegt das OS in einem Flash der durch die CPU direkt angesprochen werden kann (als ganz normaler Speicher) und davor kommt ein minimaler Initial-Loader der einfach nur das Image sucht und anspringt.
Also baust du ein NOR-Flash an eine Stelle im Adressraum, wo die CPU nach dem Reset die Ausführung beginnt und dort legst du einen Bootloader für z.B. NAND/IDE hin, der Prüfsumme berechnet und startet?

Ein full-featured BIOS finde ich jedenfalls heutzutage übertrieben. Auf keiner modernen mir bekannten Architektur (außer x86) nutzt das Betriebssystem irgendwelche Firmware-Funktionen (bei x86: ACPI, APM und was damit zusammenhängt), mal von der Informationsbeschaffung (Flash-Partitionierung, CPU-Typ und RAM-Größe) abgesehen. Das OS implementiert in der Regel alle Treiber selbst.

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 02. December 2010, 12:38
Hallo,


Ich sprach vom Augen öffnen.
Schon klar, ich hatte das auch nicht so ganz ernst gemeint. ;)

Geschichte wiederholt sich. ...
Wohl war und Dein Beispiel ist sehr gut gewählt.

Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.
Wozu? Jeder sinnvoll genutzte Interrupt ist dem System bekannt, also brauchst du in Hardware nicht doppelt arbeiten, um der CPU einmal eine interruptspezifische Funktion und die Interruptnummer mitteilen zu können.
Es gibt heutzutage viele HW-Komponenten die sehr viele Interrupts auslösen können, schau dir mal dir Gründe für MSI(-X) an, da wird man nicht für jeden Interrupt einen eigenen Handler haben wollen aber der Handler muss wissen für welchen der vielen Interrupts seiner einen HW-Komponente er gerade tätig ist. Auch könnte die selbe HW mehrfach vorhanden sein und auch da möchte man den Code nicht unbedingt doppelt haben. Ich denke den geringen Aufpreis für das Silizium sollte man zahlen wenn man dafür die volle Flexibilität bekommt.

Feste Adressen (wie bei ARM oder Z80) finde ich persönlich doof weil man dann dafür sorgen muss das an dieser Stelle auch wirklich RAM ist und weil dort eh nur ein Sprung auf den tatsächlichen Handler steht.
Bei ARM weiß ich es grad nicht, aber beim Z80 sind die Adressen nicht fest. Sie liegen nur alle zusammen in einem (kleinen) Bereich.
Bei ARM gibt es ein paar Vectoren die alle in den ersten wimre 64Bytes drin sind, man kann diese Vectoren auch in die letzte 64kB-Page legen aber man muss dort trotzdem jeweils einen Sprung auf den eigentlichen Handler hin legen und das kostet vor allem Latenz aber auch etwas nutzlosen RAM den man auch noch an einer bestimmten Adresse benötigt. Da ja auf meiner Plattform der Kernel per default ohne Paging arbeitet würde das bedeuten das ich an einer bestimmten physischen Adresse RAM vorsehen muss (x86 definiert ja auch wo überall physischer RAM verfügbar sein muss) und genau das will ich nicht, ich will auf jeden Fall das die CPU absolut keine Bedingen an das physische Speicher-Layout richtet so das der Board-Designer dort absolute Freiheit hat. Auch der Reset-Einsprungspunkt ist auf meiner CPU nicht fest definiert sondern der wird mit einem speziellen Bus-Zyklus vom Chip-Satz (der sein Board ja kennt) an die CPU übermittelt, das heißt die CPU fängt nicht einfach das arbeiten an wenn das Reset-Signal deasserted wird sondern sie bleibt in einem Power-Down-Wartezustand und wartet darauf vom Chip-Satz gesagt zu bekommen wo es los geht.

Wenn IRQ-Handler in den unteren 4 GB des Speichers liegen müssen, sehe ich da kein Problem.
Das ist in meinen Augen schon ein Problem weil das bedeutet das Du in den ersten 4GB überhaupt RAM haben musst was auf einer 64Bit-Plattform eigentlich nicht unbedingt erforderlich ist (da könnte auch der HW-MMIO-Bereich liegen).

Also baust du ein NOR-Flash an eine Stelle im Adressraum, wo die CPU nach dem Reset die Ausführung beginnt und dort legst du einen Bootloader für z.B. NAND/IDE hin, der Prüfsumme berechnet und startet?
Nein, dort liegt gleich ein vollwertiges OS-Image mit allem was nötig ist damit das OS auf die Füße kommt. Als Flash erscheint mir das http://www.sst.com/products/?inode=42062 (http://www.sst.com/products/?inode=42062) ziemlich interessant. Wenn man davon 8 Stück parallel schaltet hat man einen 32Bit-Datenbus mit 80MHz, nur ein paar Takte Initial-Latenz (bursten geht dann mit den vollen 80MHz also 320MByte/s und beliebig lang) und 32MByte Kapazität. Das macht schon ordentlich was her und kostet nur 34 Pins am FPGA, nur zum Schreiben von ganzen Blöcken muss man im FPGA einen speziellen Mechanismus einbauen aber das Lesen dürfte ziemlich simpel sein. In diesem Flash sollen mehrere OS-Images drin sein können und eines davon wird von einem Initial-Boot-Loader angesprungen, dieser Initial-Boot-Loader liegt in einem ROM-Bereich. Dazu gibt es noch etwas SRAM der dann als Stack benutzt wird weil das OS ja erst den richtigen DRAM finden und einrichten muss (der darf dann an einer beliebigen physischen Adresse liegen). Unmittelbar nach dem aufwachen aus dem Reset gibt es nur ein einziges Device das im physischen Adressraum vorhanden ist und das darf sich eine beliebige physische Adresse auswürfeln, okay ich (als Board-Designer) lege einfach mal Adresse 0 fest aber es könnte auch jede andere sein. Dieses Device enthält dann den ROM, SRAM und Flash und sendet daher die physische Adresse des ROM an die CPU zum aufwachen. Die PCI-Spezifikation macht extra bestimmte Zugeständnisse an "zum Booten benötigte Devices" die nach dem Reset bereits aktiv sind (also deren Bit 1 im Command-Register auf 1 steht was eigentlich nicht erlaubt ist).

Ein full-featured BIOS finde ich jedenfalls heutzutage übertrieben.
Full ACK. Außerdem macht das einen riesigen Haufen an zusätzlicher Programmier-Arbeit.

Das OS implementiert in der Regel alle Treiber selbst.
Eben. Das EFI-Konzept, mit den OS-unabhängigen Treibern die direkt bei der HW in einem Flash liegen, ist zwar ganz nett aber so ein interpretierter Treiber, der auch nicht wirklich zu den spezifischen OS-Konzepten passt, dürfte kaum anständige Performance bringen. Das EFI-Konzept taugt um beim OS-Booten überhaupt erst mal an Massenspeicher und Konsole kommen zu können aber zur richtigen Laufzeit vom OS ist das absolut uninteressant und dementsprechend dürftig ist die Unterstützung der HW-Komponenten-Hersteller für EFI. Einer der Gründe warum Intel sich überhaupt das EFI hat einfallen lassen war der das man mal dachte zwischen x86 und Itanium Sockel-Kompatibilität erreichen zu können (es sollte mal Boards geben auf die man einen großen x86-Prozi oder einen kleinen Itanium-Prozi stecken kann und das BIOS mit den rudimentären Treibern nicht als nativer Code doppelt vorliegen muss), der andere wichtige Grund ist natürlich das man das angestaubte RM-BIOS los werden will.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 02. December 2010, 13:22
Moin,

Es gibt heutzutage viele HW-Komponenten die sehr viele Interrupts auslösen können, schau dir mal dir Gründe für MSI(-X) an, da wird man nicht für jeden Interrupt einen eigenen Handler haben wollen aber der Handler muss wissen für welchen der vielen Interrupts seiner einen HW-Komponente er gerade tätig ist. Auch könnte die selbe HW mehrfach vorhanden sein und auch da möchte man den Code nicht unbedingt doppelt haben. Ich denke den geringen Aufpreis für das Silizium sollte man zahlen wenn man dafür die volle Flexibilität bekommt.
Okay, daran hatte ich jetzt nicht gedacht. Wobei ein Interrupthandler meist extrem kurz ist, daher ist der RAM-Verbrauch sehr gering. Zumal ich es nicht mag, wenn ein Treiber mehrere identische Hardwarekomponenten verwaltet. Da sollte man (gefühlt) den Treiber mehrfach laden können; andererseits habe ich keine Ahnung, ob sich gewisse Hardware dagegen sperrt.

Was tut man eigentlich in so einem Interrupthandler, außer in der Hardware den Datenbereich zu blockieren (damit der Treiber später kopieren kann) und ein Flag für das OS zu setzen? Kopiert man die Daten aus der Hardware in den RAM auch noch im Interrupthandler?

Bei ARM gibt es ein paar Vectoren die alle in den ersten wimre 64Bytes drin sind, man kann diese Vectoren auch in die letzte 64kB-Page legen aber man muss dort trotzdem jeweils einen Sprung auf den eigentlichen Handler hin legen und das kostet vor allem Latenz aber auch etwas nutzlosen RAM den man auch noch an einer bestimmten Adresse benötigt.
Okay, das wusste ich nicht. Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist. Schöner dehnbarer Begriff, aber wenn Interrupthandler mit einem 24-Bit-Pointer (auf einer 32-Bit-Architektur) oder 32-Bit-Pointer (bei 64 Bit) physisch erreichbar sein müssen, hab ich damit kein Problem. Die oberen Bits können ja vom Chipsatz kommen, dann verlangst du keinen RAM an einer bestimmten Stelle.

Wenn der Bereich für IRQ-Handler z.B. 16 MB groß ist, dann kann das OS ja erstmal nur Speicher alloziieren, der woanders liegt. Treiber werden ja meist nur beim Systemstart geladen oder wenn genügend Speicher frei ist. Und im schlimmsten Fall kopiert man halt den Interrupthandler woanders hin und ändert den Basiszeiger... das entspricht also einem ausgerichteten Segment für IRQ-Handler.

Nein, dort liegt gleich ein vollwertiges OS-Image mit allem was nötig ist damit das OS auf die Füße kommt. Als Flash erscheint mir das http://www.sst.com/products/?inode=42062 (http://www.sst.com/products/?inode=42062) ziemlich interessant. Wenn man davon 8 Stück parallel schaltet hat man einen 32Bit-Datenbus mit 80MHz, nur ein paar Takte Initial-Latenz (bursten geht dann mit den vollen 80MHz also 320MByte/s und beliebig lang) und 32MByte Kapazität.
Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugeben... der Vorteil wäre, dass der Flashbaustein an den ganz normalen Speicherbus angeschlossen ist und du dort dann extern (initial) und intern (vom OS aus) flashen könntest und du dir den ROM sparst. Wenn im FPGA genug Platz übrig bleibt, kannst du ja dort ein bisschen RAM erzeugen, spart den Extrabaustein.

Direktes Booten von PCI-Geräten finde ich persönlich etwas übertrieben. Mikrocontroller und SoCs tun das ja auch nicht... beim Samsung S3C24xx hast entweder einen NOR-Baustein im Adressraum oder du schließt einen NAND-Baustein an, wo die CPU dann mit einem CPU-internen Mikrocode die ersten 4 KB in einen CPU-internen SRAM kopiert und davon bootet.

Das EFI-Konzept taugt um beim OS-Booten überhaupt erst mal an Massenspeicher und Konsole kommen zu können aber zur richtigen Laufzeit vom OS ist das absolut uninteressant und dementsprechend dürftig ist die Unterstützung der HW-Komponenten-Hersteller für EFI.
In der Firmware braucht man für normale Systeme (deine Entwicklung spielt eher in Richtung leistungsfähiger Mikrocontroller) nur Treiber für Konsole (Tastatur und Textmodusbildschirm) und Bootmedium (so Dinge wie PIO-IDE-Controller, NAND-Flashbaustein und Netzwerkkarte). Das Konzept der Option-ROMs ist eigentlich optimal.

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 02. December 2010, 20:53
Hallo,


Okay, daran hatte ich jetzt nicht gedacht. Wobei ein Interrupthandler meist extrem kurz ist, daher ist der RAM-Verbrauch sehr gering.
Es geht mir nicht primär um den RAM-Verbrauch sondern darum dass das System keine Vorgaben machen sollte wo und wie die IRQ-Handler liegen sollten. Okay so ganz halte ich mich da selber nicht dran weil ich für alle HW-IRQs (und das können extrem viele sein) nur einen einzigen Handler ermögliche (mehr macht bei einem Micro-Kernel ja auch keinen Sinn), aber wenn man eine etwas allgemeinere Plattform entwickelt dann sollte man mehr Flexibilität bieten.

Zumal ich es nicht mag, wenn ein Treiber mehrere identische Hardwarekomponenten verwaltet. Da sollte man (gefühlt) den Treiber mehrfach laden können; andererseits habe ich keine Ahnung, ob sich gewisse Hardware dagegen sperrt.
Ich kann ehrlich gesagt Deine Bedenken nicht nachvollziehen. Unter Windows ist es wimre üblich das ein Treiber nur ein einziges mal existiert und die IRQ- und DPC-Handler immer eine Geräte-Individuelle Struktur als Parameter mit bekommen. Aber ich meinte eigentlich das es auch HW-Komponenten gibt die mit recht vielen IRQs umgehen. Da wären z.B. bessere Ethernet-Controller bei denen für einzelne (TCP-)Connections der Treiber einen eigenen IRQ anbindet und diesen IRQ auch auf eine bestimmte CPU fest klemmt damit die Pakete auch gleich auf der CPU im Cache landen wo dann auch der zugehörige Server-Thread läuft, das selbe erlauben auch die guten SAS-Controller welche z.B. 64 IRQs (für jede CPU einen) haben können und für jeden HDD-Zugriff kann individuell ausgewählt werden auf welcher CPU dann der zugehörige "Fertig"-IRQ kommt (das geht übrigens auch mit SATA im AHCI-Modus), auf den besseren Server-Systemen soll sowas auf jeden Fall gut funktionieren.

Was tut man eigentlich in so einem Interrupthandler ....
Also das hängt ja wohl stark von der betreffenden HW ab, zumindest simples Daten-Kopieren sollte heute kein IRQ-Handler mehr machen müssen schließlich gibt es dafür ja HW mit Busmasterfähigkeiten.

Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist.
Also mMn ist dieser Bereich entweder so groß das er den gesamten physischen Adressraum unterstützt oder es ist eine Einschränkung. Auch der Zwang das alle IRQ-Handler in einem bestimmten Bereich drin sein müssen ist IMHO nicht akzeptabel. Was ist wenn eine HW per Hot-Pluging erst später in den PC hinzugefügt (oder eingeschaltet) wird?

Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugeben
Die NOR-Flash-Teile sind schon ganz toll aber eben nicht sonderlich schnell.

der Vorteil wäre, dass der Flashbaustein an den ganz normalen Speicherbus angeschlossen ist und du dort dann extern (initial) und intern (vom OS aus) flashen könntest und du dir den ROM sparst.
Ich habe keinen normalen Speicherbus, stell Dir mein System lieber wie ein Opteron-System vor, da hat zwar jede CPU etwas Speicher aber das ist dann DDR2 oder DDR3 Speicher und an deren Datenleitungen kann man keinen NOR-Flash mit ranhängen. Flaschen passiert entweder über den FPGA per JTAG oder bei laufendem System.

Direktes Booten von PCI-Geräten finde ich persönlich etwas übertrieben.
Ich nicht, ich denke das ist genau die richtige Lösung. Es gibt ein Boot-Device, das Bestandteil des Chipsatz ist, und dieses Device bietet z.B. einen 32MB-Speicherbereich an. Die Basis-Adresse dieses Bereichs ist hardcodiert im BAR0 und wird benutzt um eine CPU aufzuwecken. In diesem Speicherbereich ist dann vorne der ROM und danach etwas RAM, etwas FRAM (für die Konfiguration) und natürlich der Flash enthalten. So lange es keine anderen Speicherbereiche im System gibt gehen eh alle Speicherzugriffe von der CPU an den Chipsatz (der dieses Boot-Device kennen muss) und wenn dann das OS-Image den RAM (der direkt an den CPUs hängt) in Betrieb nimmt werden auch die Routing-Tabellen in den CPUs konfiguriert und ab dann gibt es erst den DRAM in den dann der eigentliche OS-Kernel entpackt wird. Sobald das OS zum Leben erwacht wird das Boot-Device nicht mehr benötigt, außer noch die Konfigurationsdaten aus dem FRAM. Erst im laufenden OS wird dann der PCI-Service gestartet der den restlichen PCI-Bus nach Geräten absucht und diesen Ressourcen zuweist und Treiber startet.

Mikrocontroller und SoCs tun das ja auch nicht... beim Samsung S3C24xx hast entweder einen NOR-Baustein im Adressraum oder du schließt einen NAND-Baustein an, wo die CPU dann mit einem CPU-internen Mikrocode die ersten 4 KB in einen CPU-internen SRAM kopiert und davon bootet.
Das sind aber alles Beispiele wo die CPU-Designer schon sehr viele Vorgaben für den System-Designer machen. Ich möchte das meine CPU von außen gesagt bekommt wo es los geht und der Rest liegt dann in der Verantwortung des System-Designers.

deine Entwicklung spielt eher in Richtung leistungsfähiger Mikrocontroller
Na horch mal, ich will einen full-featured General-Purpose-PC entwickeln! Zumindest von den prinzipiellen Möglichkeiten her soll meine Plattform in dieser Liga mitspielen. Außerdem hab ich mir vorgenommen das ich einem vergleichbar ausgestattetem System (also gleiche Menge RAM, gleiche CPU-Taktfrequenz, gleiche CPU-Anzahl) zumindest auf gleicher Augenhöhe Paroli bieten kann. Du hast doch mal geschrieben das Du so verschiedene Serversysteme hast, kannst mir ja mal ne kleine Auflistung (per PM) zukommen lassen und wenn ich soweit bin suche ich mir einen adäquaten Gegner aus und wir lassen mal von http://www.eembc.org/ (http://www.eembc.org/) den CoreMark und den MultiBench gegeneinander antreten. Du darfst auch Dein OS und Compiler selber wählen. ;)


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 02. December 2010, 23:52
Hallo,

Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist.
Also mMn ist dieser Bereich entweder so groß das er den gesamten physischen Adressraum unterstützt oder es ist eine Einschränkung. Auch der Zwang das alle IRQ-Handler in einem bestimmten Bereich drin sein müssen ist IMHO nicht akzeptabel. Was ist wenn eine HW per Hot-Pluging erst später in den PC hinzugefügt (oder eingeschaltet) wird?
Einschränkung ja, KO-Kriterium nein. Was das Hot-Plugging angeht, hatte ich dazu was geschrieben - der RAM im entsprechenden Bereich wird erst dann alloziiert, wenn der restliche RAM voll ist oder er wird komplett für IRQ-Handler reserviert (16 MB sind da für mich akzeptabel). Ist der entsprechende Bereich belegt und ein Treiber wird geladen, so werden alle IRQ-Handler in einen anderen Bereich umkopiert und der Zeiger mit den in der Adresse fehlenden Bits wird neu geladen (er muss nicht null sein) oder der Treiber kann dann eben nicht geladen werden.

Das Laden von Treibern ist ein seltener Prozess, der in den allermeisten Fällen mit dem Systemstart erfolgt (ein USB-Device nutzt den IRQ des Hostcontrollers, der ist beim Systemstart vorhanden) und wenn man diesen RAM-Bereich nicht direkt als erstes rausgibt, dann ist dort auch immer etwas frei, solange das System nicht an der Lastgrenze steht. Wieder: Einschränkung ja, KO-Kriterium nein. ;-)

Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugeben
Die NOR-Flash-Teile sind schon ganz toll aber eben nicht sonderlich schnell.
Ist das ein Problem? Der initiale Bootloader kann doch das OS erstmal in den RAM kopieren, ehe du deine Lebendgeburt vornimmst. Wenn du mit 32 MB für mehrere OS-Images rechnest, dann dürfte ein OS-Image vielleicht 8-12 MB haben... bei 1 GB RAM kannst du das nun wirklich locker verschmerzen.

Ich habe keinen normalen Speicherbus, stell Dir mein System lieber wie ein Opteron-System vor, da hat zwar jede CPU etwas Speicher aber das ist dann DDR2 oder DDR3 Speicher und an deren Datenleitungen kann man keinen NOR-Flash mit ranhängen. Flaschen passiert entweder über den FPGA per JTAG oder bei laufendem System.
Gut, das ist ein KO-Kriterium. Wobei du eine Art Speicherbus sicherlich haben wirst, um MMIO da ranzuhängen; wenn im physischen Adressraum Geräte auftauchen, kann das auch der Flashbaustein.

Direktes Booten von PCI-Geräten finde ich persönlich etwas übertrieben.
Ich nicht, ich denke das ist genau die richtige Lösung. Es gibt ein Boot-Device, das Bestandteil des Chipsatz ist, und dieses Device bietet z.B. einen 32MB-Speicherbereich an. [...]
Ich würde einfach ein Stück Adressraum mit dem ROM vorbelegen. Das ist einfacher. Und wenn einem der Adressraum wirklich zu knapp ist, dann kann das OS ja auch das Adressbit wieder in physischen RAM (oder MMIO-Bereiche) zeigen lassen, statt in den Bootcode.

Mikrocontroller und SoCs tun das ja auch nicht... beim Samsung S3C24xx hast entweder einen NOR-Baustein im Adressraum oder du schließt einen NAND-Baustein an, wo die CPU dann mit einem CPU-internen Mikrocode die ersten 4 KB in einen CPU-internen SRAM kopiert und davon bootet.
Das sind aber alles Beispiele wo die CPU-Designer schon sehr viele Vorgaben für den System-Designer machen. Ich möchte das meine CPU von außen gesagt bekommt wo es los geht und der Rest liegt dann in der Verantwortung des System-Designers.
Gut, ist eine Designentscheidung. Normalerweise willst du ein Board möglichst einfach stricken und wenn die passende Unterstützung in der CPU schon vorhanden ist, dann ist das halt einfacher und billiger. Je flexibler die CPU ist, umso komplizierter wird der Chipsatz.

deine Entwicklung spielt eher in Richtung leistungsfähiger Mikrocontroller
Na horch mal, ich will einen full-featured General-Purpose-PC entwickeln!
Ääh, das war nicht abwertend gemeint. Du kannst auch einen ARM9 mit VGA-Ausgang, USB-Tastatur/-Maus und einer IDE-Festplatte als General-Purpose-PC nutzen... und trotzdem ist der ARM ein Mikrocontroller. Definiere mir bitte mal den Unterschied, den es da für dich gibt. ;-)

Zumindest von den prinzipiellen Möglichkeiten her soll meine Plattform in dieser Liga mitspielen. Außerdem hab ich mir vorgenommen das ich einem vergleichbar ausgestattetem System (also gleiche Menge RAM, gleiche CPU-Taktfrequenz, gleiche CPU-Anzahl) zumindest auf gleicher Augenhöhe Paroli bieten kann. Du hast doch mal geschrieben das Du so verschiedene Serversysteme hast, kannst mir ja mal ne kleine Auflistung (per PM) zukommen lassen und wenn ich soweit bin suche ich mir einen adäquaten Gegner aus und wir lassen mal von http://www.eembc.org/ (http://www.eembc.org/) den CoreMark und den MultiBench gegeneinander antreten. Du darfst auch Dein OS und Compiler selber wählen. ;)
Muss ich ablehnen. Mein 286er mit Xenix als Server-OS ist derzeit kaputt und die ganzen dicken Server gehören der Firma, bei der ich gerade Praktikum mache. Da hab ich keinen Shellzugang, ich hab da nur dutzende Festplatten reingeschraubt. ;-)

Du meinst aber so geschätzte 100 MHz? Ich hab noch ein Dell-System mit nem Pentium aus der Zeit rumstehen, den könnte man nochmal wiederbeleben... ansonsten hab ich nur sehr kleine (33/40/66 MHz) 486er-Systeme rumstehen. Die sind ungeeignet für Benchmarks. Und lagern auf dem Dachboden - Sommer 50°C, Winter -20°C. Für richtige Multi-CPU-Systeme musst du FlashBurn fragen, der hatte wohl sowas.

Wobei ein Bekannter eine Dual-386-Maschine hat. Die trickst wohl in Hardware soweit rum, dass auch Win95 davon Vorteile hat (er meinte, Anno 1602 lief flüssig). Gab schon seltsame Tiere damals.

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 03. December 2010, 12:44
Hallo,


der RAM im entsprechenden Bereich wird erst dann alloziiert, wenn der restliche RAM voll ist oder er wird komplett für IRQ-Handler reserviert (16 MB sind da für mich akzeptabel).
Ich gehe jetzt mal von einem Monolithen aus: Du willst also alle IRQ-Handler von allen Treiber in einem bestimmten Speicherbereich zusammen haben? Virtuell oder physisch? Wie soll das eigentlich gehen? Der IRQ-Handler liegt doch irgendwo mitten im Code vom Treiber, für den Compiler ist das eine Funktion wie jede andere auch. Um das zu bewerkstelligen müsstest Du IMHO vom Compiler bis zum Datei-Format ne ganze Menge Zeugs anfassen. Und das nur um im IRQ-Controller ein paar Bits einzusparen. Das kann ich irgendwie nicht nach vollziehen. Ich verlange z.B. das die IRQ-Handler auf 256Bytes ausgerichtet sind um mir die untersten 8 Bits zu sparen aber die oberen Bits bleiben definitiv erhalten. Ich könnte jetzt überlegen das auf einem konkreten System wo der Bus nur 48Bit an physischen Adressen unterstützt (HyperTransport hat ja auch so eine Beschränkung) das ich mir da bei diesen Registern die oberen Bits sparen könnte, weil so eine Adresse eh nicht möglich ist, aber grundsätzlich finde ich ist das an der falschen Stelle gespart.

Einschränkung ja, KO-Kriterium nein. ;-)
Im Prinzip ja, aber zu welchem Preis?

Der initiale Bootloader kann doch das OS erstmal in den RAM kopieren, ehe du deine Lebendgeburt vornimmst.
Der initiale Boot-Loader soll nur das OS-Image anspringen und vorher noch den Stack vorbereiten, macht also ungefähr so viel wie eine minimale crt0.asm die mehrere main-Funktionen zur Auswahl hat. Dieser Initial-Boot-Loader soll keine Veränderungen am System vornehmen. Erst der Code im OS-Image initialisiert überhaupt die DRAM-Controller damit überhaupt normaler Speicher verfügbar ist. Der Code im OS-Image soll nur von den Ressourcen abhängen die das Boot-Device grundsätzlich zur Verfügung stellt, alles was er zusätzlich benötigt muss er selber erst vorbereiten.

dann dürfte ein OS-Image vielleicht 8-12 MB haben
Bis es wirklich so viel ist dürfte es ne Weile dauern, schließlich muss ich das ja alles auch selber programmieren.

Wobei du eine Art Speicherbus sicherlich haben wirst, um MMIO da ranzuhängen;
Ja natürlich, ich hab da an PCI oder gar PCI-Express gedacht. Damit kann ich dann ganz normale Standard-HW (wie z.B. SATA-Controller oder Ethernet-Karten) anbinden. Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.

wenn im physischen Adressraum Geräte auftauchen, kann das auch der Flashbaustein.
Eben deshalb soll das Boot-Device ein PCI-Gerät sein, wenn auch nur logisch weil es ja integraler Bestandteil des Chipsatzes ist.

Ich würde einfach ein Stück Adressraum mit dem ROM vorbelegen. Das ist einfacher.
Welches Stück vom Adressraum? Das ist genau die Frage die bei mir nicht der CPU-Designer sondern der System-Designer klären soll. Klar ist es einfacher wenn der CPU-Designer sagt die CPU fängt an der physischen Adresse 0 an zu arbeiten wenn sie aus dem Reset kommt aber es zwingt dem System-Designer dazu dafür zu sorgen das genau dort auch wirklich der Initial-Boot-Loader liegt. Das zweite Problem ist: Was machen die anderen CPUs in einem SMP-System wenn diese auch aufwachen sollen? Ich denke man benötigt eh einem Mechanismus um eine CPU an einer flexiblen Adresse aufwachen zu lassen und da kann ich diesen Mechanismus auch gleich für die erste CPU benutzen und spare mir die CPU-Vorgaben für das physische Speicherlayout.

Ääh, das war nicht abwertend gemeint.
Schon klar.

Definiere mir bitte mal den Unterschied, den es da für dich gibt. ;-)
Da gibt es absolut keinen Unterschied. Was man aus einer CPU macht hängt in erster Linie vom System ab in dem sie verbaut ist.

Muss ich ablehnen.
Schade, nunja da muss ich mir eben jemand anderes suchen.

Du meinst aber so geschätzte 100 MHz?
Ja, das wäre so ungefähr die Gewichtsklasse die ich mit nem FPGA einigermaßen realistisch erreichen kann. Wobei wohl ein 486 kaum ein echter Gegner ist weil er doch eine recht simple Pipeline hat und einen recht schwachen Befehlssatz, mein erster FPGA-Versuch wird mit Sicherheit keine Bessere Pipeline haben aber auf jeden Fall einen deutlich leistungsfähigeren Befehlssatz und eben 64 Register. So lange ich keinen zu großen Mist baue müsste der 486 schon mit mindestens der doppelten Taktfrequenz wie mein FPGA kommen wenn er eine reelle Chance haben soll.

Für richtige Multi-CPU-Systeme musst du FlashBurn fragen, der hatte wohl sowas.
Also wenn ich doch in die Richtung um 100MHz hin komme dann wäre ein 4fach PentiumPro (mit kleinem Multiplikator) genau das was ich mir als herausfordernden Gegner vorstelle. Der hat zwar nach außen den schwachen x86-Befehlssatz aber intern kann er gut Parallelisieren, kann Out-of-Order-Execution und Register-Renaming. Wenn ich zumindest in gewissem Grad auch Parallelisieren kann dann sollte ein PentiumPro bei gleichem Takt tatsächlich zu schlagen sein weil all die restlichen Dinge die der PentiumPro in HW macht bei mir der Compiler tun kann.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 03. December 2010, 14:30
Hallo,

Du willst also alle IRQ-Handler von allen Treiber in einem bestimmten Speicherbereich zusammen haben? Virtuell oder physisch? [...] Und das nur um im IRQ-Controller ein paar Bits einzusparen.
Das hängt davon ab, wie teuer die Bits im IRQ-Controller sind. Ansonsten hast du natürlich recht. Du tauschst Softwarekomplexität gegen Hardwarekomplexität ein.

Allerdings sehe ich kein Problem damit, wenn ein Treiber seinen IRQ-Handler im Betriebssystem registrieren muss (das OS kümmert sich dann um den IRQ-Controller) und dieser Code dann an einer bestimmten Stelle im RAM liegen muss. Das kann ja auch zur Laufzeit geschehen (wie TSR-Programme, die den residenten Teil ja auch erstmal irgendwohin kopieren, damit er nicht im Weg rumliegt).

aber grundsätzlich finde ich ist das an der falschen Stelle gespart.
Hast du recht.

Der initiale Boot-Loader soll nur das OS-Image anspringen und vorher noch den Stack vorbereiten, macht also ungefähr so viel wie eine minimale crt0.asm die mehrere main-Funktionen zur Auswahl hat. Dieser Initial-Boot-Loader soll keine Veränderungen am System vornehmen. Erst der Code im OS-Image initialisiert überhaupt die DRAM-Controller damit überhaupt normaler Speicher verfügbar ist.
Okay, das ist eine Designentscheidung. Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.

Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.
Sehe ich als overkill an, aber OK.

Das ist genau die Frage die bei mir nicht der CPU-Designer sondern der System-Designer klären soll.
Gefühlt wird es genau einen einzigen Chipsatz für deine CPU geben, auf einem einzigen Boardtyp. Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)

Klar ist es einfacher wenn der CPU-Designer sagt die CPU fängt an der physischen Adresse 0 an zu arbeiten wenn sie aus dem Reset kommt aber es zwingt dem System-Designer dazu dafür zu sorgen das genau dort auch wirklich der Initial-Boot-Loader liegt. Das zweite Problem ist: Was machen die anderen CPUs in einem SMP-System wenn diese auch aufwachen sollen? Ich denke man benötigt eh einem Mechanismus um eine CPU an einer flexiblen Adresse aufwachen zu lassen und da kann ich diesen Mechanismus auch gleich für die erste CPU benutzen und spare mir die CPU-Vorgaben für das physische Speicherlayout.
Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen muss, wenn CPURESET deaktiviert wird. Wenn du SYSTEMRESET aktivierst, dann wird der Datenbus statisch vorbelegt, wenn du sekundäre CPUs bootest, legt die primäre CPU halt ein Wort auf den Datenbus und löst das CPURESET der anderen CPU.

Du möchtest dem Systemdesigner maximale Freiheit lassen, implementierst aber den PCI-Hostcontroller direkt innerhalb der CPU. Damit legst du fest, dass selbst eine schnellere CPU niemals ein anderes (evtl. schnelleres) Bussystem nutzen kann. Außerdem ist deine Speicheradressierung bekanntermaßen so inkompatibel zum Rest der Welt, dass du schon das OS sehr tief mit der CPU verheiraten musst. Das sehe ich als Widerspruch an. Wenn du natürlich einfach Bussysteme einsparen möchtest, dann ist das verständlich.

Für richtige Multi-CPU-Systeme musst du FlashBurn fragen, der hatte wohl sowas.
Also wenn ich doch in die Richtung um 100MHz hin komme dann wäre ein 4fach PentiumPro (mit kleinem Multiplikator) genau das was ich mir als herausfordernden Gegner vorstelle.
Finde mal solche Systeme, ich habe jedenfalls keins. Was ich habe, können wir gerne als Vergleich benutzen.

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 03. December 2010, 15:27
Hallo,


Das hängt davon ab, wie teuer die Bits im IRQ-Controller sind.
Wenn der IRQ-Controller tausende IRQs verwalten kann und jeder IRQ eine eigenen Adresse haben kann dann kommen da schon ein paar Transistoren zusammen aber da nimmt man dann eh SRAM o.ä. für. Da ich ja nur einen IRQ-Handler (im Micro-Kernel) für alle HW-IRQs hab benötige ich im IRQ-Controller auch nur ein einziges Register dafür, da ist der Preis auf jeden Fall sehr gering.

Allerdings sehe ich kein Problem damit, wenn ein Treiber seinen IRQ-Handler im Betriebssystem registrieren muss (das OS kümmert sich dann um den IRQ-Controller) und dieser Code dann an einer bestimmten Stelle im RAM liegen muss. Das kann ja auch zur Laufzeit geschehen (wie TSR-Programme, die den residenten Teil ja auch erstmal irgendwohin kopieren, damit er nicht im Weg rumliegt).
Klar meldet der Treiber seinen IRQ-Handler beim OS an aber er gibt dabei auch nur den Einsprungspunkt für seinen IRQ-Handler bekannt und nicht wie viele Code-Bytes der belegt und was für Funktionen dann sonst noch (von dieser Handler-Funktion) so alles aufgerufen werden können. Den IRQ-Handler aus einem Treiber heraus zu filetieren stelle ich mir extremst kompliziert vor. Bei den TSR (ich nehme mal an Du meinst das was zu DOS-Zeit so üblich war) ist die Sache ganz anders, war war der TSR-Teil quasi ein eigenständiges Programm das in das Haupt-Programm mit integriert wurde.

Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.
Da bleibt ja eigentlich nur die DRAM-Initialisierung, alles andere wird erst später im laufenden OS in Betrieb genommen. Aber so ein bisschen Recht hast Du schon, wenn ich die Möglichkeit habe in dem Flash mehrere Images zu haben dann könnte da auch eines mit system-spezifischen Funktionen sein. Das heißt das ich eine Art Firmware hätte die nicht per INT o.ä. benutzt wird sondern eine Tabelle mit den unterstützen Funktionen und den zugehörigen Einsprungspunkten am Anfang bietet. Da könnten z.B. Funktionen wie RAM-Größe ermitteln oder RAM initialisieren oder eventuell auch Anzahl der CPUs ermitteln und CPU in Betrieb nehmen drin sein. Diesen Gedanken muss ich mal notieren. So lange ich genau ein OS auf genau einem System habe ist das uninteressant aber wenn ich doch mal unterschiedliche HW-Implementierungen haben sollte könnte diese Idee sich als nützlich erweisen. Danke!

Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.
Sehe ich als overkill an, aber OK.
Ich meine ja nicht das ich zur Verbindung der CPUs untereinander und mit dem Chipsatz PCI(e) einsetzen will sondern nur das alle physischen CPUs so einen PCI-Config-Space zur Verfügung stellen. Die CPUs wären dann alle im virtuellen PCI-Bus 0 und das Device 31 wäre der Chip-Satz in dem eine oder mehrere PCI-Funktionen dann als PCI2PCI-Bridges einen Root-Port nach draußen bieten.

Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)
Die Personalunion ist einfach ein Zwang der Umstände, mir hilft ja keiner. ;) Und wenn ich drauf aus wäre es mir einfach zu machen wäre ich nicht intelligent geworden! ;)

Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen muss
Ich habe keinen Datenbus der alle CPUs verbindet, ich möchte Punkt-zu-Punkt-Verbindungen benutzen und dafür die tollen GBit-SERDES der aktuellen FPGAs benutzen. Ich muss das also eh alles mit Messages machen. Ich kann Dir nur empfehlen mal die Spezifikationen zu HyperTransport und PCI-Express zu lesen, die bauen beide auf die selben PCI-Konzepten (Management, Config-Space usw.) auf auch wenn die Realisierungen sich in vielen Details doch extrem unterscheiden (auch gegenüber dem klassischen parallelen Original).

Finde mal solche Systeme, ich habe jedenfalls keins.
Hab ich bereits bemerkt, man findet keine PentiumPro-Server-Systeme mehr bei ebay & co.

Was ich habe, können wir gerne als Vergleich benutzen.
Danke für Dein Angebot.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 03. December 2010, 16:53
Hallo,

Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.
Da bleibt ja eigentlich nur die DRAM-Initialisierung, alles andere wird erst später im laufenden OS in Betrieb genommen.
Mir fallen da spontan noch die Bootmedien ein. Schließlich will man nicht immer ein vorgefertigtes OS booten können (was, wenn man es kaputtkonfiguriert hat?), sondern auch mal von so Dingen wie USB-Festplatten, Speicherkarten, TFTP-Server und so. Wenn du deinen Flashbaustein nur als "ein weiteres Bootmedium" betrachtest, dann hast du die Trennung von Firmware wieder drin.

Aber so ein bisschen Recht hast Du schon, wenn ich die Möglichkeit habe in dem Flash mehrere Images zu haben dann könnte da auch eines mit system-spezifischen Funktionen sein. Das heißt das ich eine Art Firmware hätte die nicht per INT o.ä. benutzt wird sondern eine Tabelle mit den unterstützen Funktionen und den zugehörigen Einsprungspunkten am Anfang bietet. Da könnten z.B. Funktionen wie RAM-Größe ermitteln oder RAM initialisieren oder eventuell auch Anzahl der CPUs ermitteln und CPU in Betrieb nehmen drin sein.
Du betrachtest also dein gesamtes Betriebssystem wie bei x86 das BIOS. Finde ich unpraktisch. Ich würde im Flash eine Firmware bauen, der hardwarespezifisch ist und DRAM, Console und Bootmedium initialisiert, dann eventuell eine Auswahl erlaubt. Das Bootmedium kann ja dein OS im gleichen Flash sein, das schließt sich nicht aus. Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.

Das ist nicht nur sinnvoll, um ein zweites OS auf deine CPU zu bringen, sondern auch, um dein OS auf eine ähnliche CPU (Version 2) zu bringen.

Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)
Die Personalunion ist einfach ein Zwang der Umstände, mir hilft ja keiner. ;) Und wenn ich drauf aus wäre es mir einfach zu machen wäre ich nicht intelligent geworden! ;)
Ja, dank des kleinen Teams muss doch eine sinnvolle, gemeinsame, Zusammenarbeit möglich sein! ;-) Oder lebt ihr in verschiedenen Staaten ohne Internet?

Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen muss
Ich habe keinen Datenbus der alle CPUs verbindet, ich möchte Punkt-zu-Punkt-Verbindungen benutzen und dafür die tollen GBit-SERDES der aktuellen FPGAs benutzen. Ich muss das also eh alles mit Messages machen. Ich kann Dir nur empfehlen mal die Spezifikationen zu HyperTransport und PCI-Express zu lesen, die bauen beide auf die selben PCI-Konzepten (Management, Config-Space usw.) auf auch wenn die Realisierungen sich in vielen Details doch extrem unterscheiden (auch gegenüber dem klassischen parallelen Original).
Mit HyperTransport hab ich mich mal am Rande befasst, aber da gibt es natürlich keinen klassischen Bus mehr. Okay.

Gruß,
Sebastian
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 03. December 2010, 19:21
Hallo,


Mir fallen da spontan noch die Bootmedien ein. Schließlich will man nicht immer ein vorgefertigtes OS booten können (was, wenn man es kaputtkonfiguriert hat?), sondern auch mal von so Dingen wie USB-Festplatten, Speicherkarten, TFTP-Server und so. Wenn du deinen Flashbaustein nur als "ein weiteres Bootmedium" betrachtest, dann hast du die Trennung von Firmware wieder drin.
So lange ich nur mein eines OS habe bin ich der Meinung das ich keine Firmware benötige aber für später hast Du grundsätzlich recht. Das Problem ist das die Firmware fast alles machen muss was das OS auch tut, also DRAM anschalten, PCI-Devices traversieren und mit Ressourcen versorgen (zumindest die nützlichen) und dann auch einige Devices in Betrieb nehmen (wie z.B. AHCI-SATA-Controller oder USB-Controller). Für den ersten Moment sehe ich da vor allem doppelte Arbeit (oder zumindest einen Mehraufwand) weil die selbe Funktionalität ja mehrfach vorhanden sein muss (sicher kann man in vielen Fällen auf den selben Quell-Code zurückgreifen). Diese zusätzliche Arbeit bringt mir aber im ersten Schritt keinen Nutzwert sondern verlängert nur die Boot-Zeit. Wenn ich irgendwann mal soweit bin das ich auch mal ein OS von woanders booten möchte kann ich dann immer noch ein Firmware-Image parallel zu dem festen OS-Image in den Flash packen. Ob ich dann versuche einen vorhandenen Boot-Loader (wie z.B. uboot) zu portieren oder mich am vorhandenen OS-Quell-Code schadlos halte steht noch in den Sternen aber möglich ist es auf jeden Fall. Schön wäre es auch wenn diese Firmware in der Lage ist eine GUI als Auswahlmenü für die vorhandenen Flasch-Images anzubieten.

Du betrachtest also dein gesamtes Betriebssystem wie bei x86 das BIOS.
Ja, eine Trennung macht IMHO für den ersten Anlauf absolut keinen Sinn.

Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.
Genau da sehe ich ein Problem, ich möchte nicht das irgendeine Firmware dem OS sagt wo was ist sondern ich möchte dass das OS selber bestimmen kann wo was hinkommt. Die einzigste Ressource die da ist wenn das OS-Image angesprungen wird ist das Boot-Device selber und alles andere kann das OS so im physischen Adressraum verteilen wie es das möchte. Deshalb ja auch alles als PCI-Device damit das OS einen generischen Mechanismus hat mit dem jeder Ressource (selbst dem normalen RAM) eine Adresse zugewiesen werden kann.

Ja, dank des kleinen Teams muss doch eine sinnvolle, gemeinsame, Zusammenarbeit möglich sein! ;-) Oder lebt ihr in verschiedenen Staaten ohne Internet?
Ich denke dieses Team kann recht gut kooperieren und kommunizieren. Daher wurde beschlossen wer für was zuständig ist und einer dieser Beschlüsse lautet das die CPU absolut keine Vorgaben macht was den physischen Adressraum angeht, es gibt also absolut keine hartcodierten Adressen in der CPU. Ich denke dieser Beschluss ist zum Vorteil aller beteiligten Entwickler. ;)


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 03. December 2010, 23:00
Hallo,


Das Problem ist das die Firmware fast alles machen muss was das OS auch tut, also DRAM anschalten, PCI-Devices traversieren und mit Ressourcen versorgen (zumindest die nützlichen) und dann auch einige Devices in Betrieb nehmen (wie z.B. AHCI-SATA-Controller oder USB-Controller).
Im einfachsten Fall wird der DRAM eingeschaltet und von einem fest vorgegebenen Bootdevice gestartet, welches von der Firmware aktiviert werden muss. Das OS sollte niemals den DRAM einschalten müssen - das wurde von der Firmware getan; keine doppelte Arbeit. Und das Bootdevice zu aktivieren muss ohnehin getan werden; wenn die Firmware im gleichen Flash liegt wie das OS-Image, dann ist das nichtmal nötig. Auch keine doppelte Arbeit. ;-)

Für den ersten Moment sehe ich da vor allem doppelte Arbeit (oder zumindest einen Mehraufwand) weil die selbe Funktionalität ja mehrfach vorhanden sein muss (sicher kann man in vielen Fällen auf den selben Quell-Code zurückgreifen). Diese zusätzliche Arbeit bringt mir aber im ersten Schritt keinen Nutzwert sondern verlängert nur die Boot-Zeit.
Ob nun eine Firmware den RAM aktiviert und dann ein OS mit Informationen darüber startet oder ob die Firmware "Code" ausführt, der den RAM aktiviert, Informationen darüber rauspult und dann zu einem OS mutiert, finde ich nicht als Unterschied.

Fass den Begriff "Firmware" nicht zu weitgreifend auf. Unter Firmware verstehe ich den Code, der die Initialisierung der Hardware in einen Zustand, in dem Code ohne tiefgreifende Kenntnis der Hardware ausgeführt werden kann, vornimmt. Mehr nicht. Alles, was darüber geht (also TUI/GUI, Bootmedienauswahl, ...) fällt für mich unter den Begriff BIOS.

Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.
Genau da sehe ich ein Problem, ich möchte nicht das irgendeine Firmware dem OS sagt wo was ist sondern ich möchte dass das OS selber bestimmen kann wo was hinkommt.
Die Firmware gibt dem OS nicht vor, wo die OS-Bestandteile hingehören, sondern die Firmware verrät dem OS, wieviel RAM vorhanden ist und wo er sich (physisch) befindet. Mal abgesehen ist es nicht "irgendeine" Firmware, sondern genau die Firmware, die als einzige genaue Informationen über die Hardware weiß.

Theoretisch sollte ein OS einigermaßen agnostisch gegenüber der Plattform sein. Außerdem legt dein OS am Ende sowieso alles wesentliche an fixe Adressen, oder möchtest du die Position des RAMs bei jedem Neustart von einem Zufallsgenerator auswürfeln? Im Betrieb wirst du die physische Basisadresse des Speichers auch nicht ändern wollen.

Lass doch die Firmware einen vernünftigen Zustand herstellen, den das OS ohnehin herstellen würde.

Daher wurde beschlossen wer für was zuständig ist und einer dieser Beschlüsse lautet das die CPU absolut keine Vorgaben macht was den physischen Adressraum angeht, es gibt also absolut keine hartcodierten Adressen in der CPU. Ich denke dieser Beschluss ist zum Vorteil aller beteiligten Entwickler. ;)
CPU != Chipsatz != OS.

An einer Stelle machst du definitiv Vorgaben über den physischen Adressraum. Meine Empfehlung wäre, diese Vorgabe dort zu machen, wo sie hingehört. Die Firmware wird mit dem Chipsatz getauscht, CPU und OS eher nicht. Also gehört genau sowas in die Firmware und nicht in die CPU und nicht in das OS.

Damit wär glaub ich alles gesagt. ;-)

Gruß,
Svenska
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 04. December 2010, 10:31
Hallo,


Das OS sollte niemals den DRAM einschalten müssen - das wurde von der Firmware getan
Ich möchte aber nicht das jemand anderes als das OS den RAM einschaltet und damit seine physische Adresse festlegt. Da ich im Kernel-Mode per Default keinen virtuellen Speicher hab arbeiten bei meinem OS alle Exception/Syscall/IRQ-Handler (also der gesamte Kernel-Code) direkt mit den physischen Adressen, deswegen sollte das OS da auch die Kontrolle darüber haben wo der RAM liegt. Ich weiß das ist eine merkwürdige Design-Entscheidung aber so ist es nun mal. Bei klassischen Flat-Memory-Systemen liegt der Kernel zur Laufzeit immer an der selben virtuellen Adresse egal was das für ein Rechner ist und egal wie viel RAM dort drin steckt und egal wie der RAM im physischen Adressraum verteilt ist. Diesen Luxus habe ich nicht.

keine doppelte Arbeit. ;-)
Es geht mir weniger darum das ich eventuell 2 mal Code für die selbe Sache erstellen muss sondern darum das ich 2 unabhängige Images erstellen müsste. Außerdem bräuchte ich dann auch 2 verschiedene Aufrufkonventionen, da das Firmware-Image ja nur mit funktionierenden Stack aber ohne irgendeine aktivierte HW aufgerufen wird müsste man das OS-Image mit anderen Informationen versorgen da dort ja zumindest schon mal der RAM aktiv ist. Das ist sicher alles nicht so viel Arbeit aber ich habe im ersten Anlauf davon definitiv keinen Nutzen also kann ich auch erst mal alles an einem Stück lassen. Wenn ich später z.B. mal 2 verschiedene HW-Revisionen unterstützen möchte und dafür eine Art BIOS mit system-spezifischen Funktionen (wie RAM aktivieren) anbieten möchte kann ich mir immer noch ein geeignetes Konzept überlegen und aus dem OS-Image etwas Code rauswerfen und dafür BIOS-Calls o.ä. einbauen.

Fass den Begriff "Firmware" nicht zu weitgreifend auf. Unter Firmware verstehe ich den Code, der die Initialisierung der Hardware in einen Zustand, in dem Code ohne tiefgreifende Kenntnis der Hardware ausgeführt werden kann, vornimmt. Mehr nicht. Alles, was darüber geht (also TUI/GUI, Bootmedienauswahl, ...) fällt für mich unter den Begriff BIOS.
Okay, dann haben wir gestern ein ganz klein wenig aneinander vorbei geschrieben. Im Endeffekt sind das aber alles nur Namen, was für mich erst mal wichtig ist sind die Funktionalitäten und die will ich erst mal ein einem Stück zusammenhalten. Dann habe ich halt ein OS-Image das ein klein wenig Firmware-Code, etwas Kernel-Geburtshelfer-Code und einen gepackten Kernel plus gepackter Personality enthält. Für den ersten Anlauf ist das sicher komplex genug da möchte ich mir nicht auch noch Gedanken darüber machen wie ich das über mehrere Images verteile und wie die sich gegenseitig benutzen.

Mal abgesehen ist es nicht "irgendeine" Firmware, sondern genau die Firmware, die als einzige genaue Informationen über die Hardware weiß.
Welche Informationen denn? Aus meiner Sicht ist der physische Adressraum absolut leer, vom Boot-Device einmal abgesehen, wenn das OS-Image gestartet wird. Selbst wenn der Kernel aktiviert wurde und das OS zum ersten mal 100% einsatzbereit ist ist da nur der RAM hinzugekommen. Alle anderen HW-Komponenten egal ob im Chipsatz integriert, als On-Board-Chip oder als Einsteckkarte sind zu diesem Zeitpunkt noch im Reset. Mit Ausnahme von der Methode mit der der RAM aktiviert werden muss (was ja von der konkreten HW abhängt) gibt es da IMHO keine Information die die Firmware haben könnte. Beim x86-PC wo das BIOS schon alles mögliche gemacht hat bevor auch nur mal ein Boot-Sektor von irgendeinem Massenspeicher geladen wird (und danach noch Unmengen an Services anbietet) ist das natürlich was völlig anderes. Wenn ich irgendwann später wirklich mal eine Art BIOS programmieren will das dann Dinge wie booten von TFTP oder USB-Stick anbieten soll dann muss dieses BIOS eben ein OS-Image von irgendwo laden und anschließend alle HW-Komponenten wieder deaktivieren (das entspricht auch dem Konzept von uboot) und danach dann dieses OS-Image anspringen. Auch dieses OS-Image würde das System so vorfinden wie ein OS-Image aus dem Flash. Wohin dieses Image dafür geladen werden müsste muss ich mir mal noch in Ruhe überlegen, da das Boot-Device nebem ROM, FRAM und Flash ja eh auch normalen RAM anbieten muss könnte ich auch einfach einen kleinen DRAM-Chip anstatt SRAM benutzen und einen simplen DRAM-Controller integrieren der nicht konfiguriert werden muss. Dann hätte ich genug RAM-Platz um so ein externes OS-Image auch im Boot-Device liegen zu haben.

Theoretisch sollte ein OS einigermaßen agnostisch gegenüber der Plattform sein.
Ein sehr guter Standpunkt, aber nur wenn es sich nur um schwachen Agnostizismus handelt (ich hab extra mal bei Wikipedia nachgelesen). Auch ein weiterer Grund warum ich jede Ressource als PCI-Device haben möchte, so braucht das OS (bzw. die Personality) am Anfang nichts zu wissen und kann trotzdem alles aus eigener Kraft in Erfahrung bringen.

oder möchtest du die Position des RAMs bei jedem Neustart von einem Zufallsgenerator auswürfeln?
Warum nicht? Das wäre doch mal was wirklich neues wenn ASLR sogar so weit geht.

Im Betrieb wirst du die physische Basisadresse des Speichers auch nicht ändern wollen.
Warum nicht? Vielleicht möchte ich ja auch beim RAM Hot-Pluging unterstützen.

Lass doch die Firmware einen vernünftigen Zustand herstellen, den das OS ohnehin herstellen würde.
Definiere Bitte "vernünftiger Zustand". Dazu müsste die Firmware aber Informationen über das OS haben und genau das will ich nicht. Es ist auf jeden Fall sinnvoll wenn die Firmware nötige Werkzeuge anbietet aber die führende Hand bleibt beim OS.

CPU != Chipsatz != OS.
Ganz genau, Teile und Herrsche! Jedes Teil hat ganz genau definierte Zuständigkeiten. CPU und Chipsatz bieten die Mittel und das OS hat das Konzept wie diese Mittel zu benutzen sind.

Meine Empfehlung wäre, diese Vorgabe dort zu machen, wo sie hingehört. Die Firmware wird mit dem Chipsatz getauscht, CPU und OS eher nicht. Also gehört genau sowas in die Firmware und nicht in die CPU und nicht in das OS.
Warum soll die Firmware Entscheidungen treffen mit deren Konsequenzen dann das OS leben muss? Nein, das finde ich nicht gut. Die Entscheidungen soll genau der treffen der auch die Konsequenzen tragen muss!

Das ist meine Meinung zu diesem Thema. ;)


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: DeepDancer am 06. December 2010, 14:40
HI Ihrs alle,

ich hab da auch mal nen Fragchen dazu.

Gibt es irgendwo mal ne Logikschaltung für ne CPU?
Ich durchforste seit Tagen schon das Netz aber iwie will sich da nix passendes finden. Das MyCPU.eu-Projekt ist auch nicht ganz das was ich suche, denn dort wird ja ein (oder mehrere) EPROM genommen für den MicroCode, aber genau das will ich eben nicht sehen...

Versteht Ihr was ich meine?

Also falls mal noch jemand ne Idee hat wo ich weiterkommen könnte?

Grüße,
DeepDancer
Titel: Re:Eigene CPU
Beitrag von: Programm Noob am 06. December 2010, 15:02
such mal nach 8081
Da finden sich Schaltpläne für

PNoob
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 06. December 2010, 16:32
Hallo,


Gibt es irgendwo mal ne Logikschaltung für ne CPU?
Auf http://opencores.org/projects (http://opencores.org/projects) gibt es den Unterpunkt "Processor", da sind von ganz Klein bis Mittelgroß ne Menge unterschiedlicher CPUs drin. Wenn Du mal was richtig Großes sehen willst dann musst Du bei SUN nach dem Quell-Code der letzten SPARC-CPUs schauen (da fällt mir jetzt leider nicht der Name ein). Für all das muss man aber VHDL oder Verilog können.

Als Logic-Schaltplan (so als würde man das wirklich mit TTL aufbauen) wirst Du CPUs kaum finden, selbst ziemlich kleine CPUs würden bei normaler Druck-Auflösung schon extrem viele Quadratmeter Papier belegen, diese Art der Hardware-Entwicklung ist heutzutage absolut nicht mehr praktikabel.


Grüße
Erik
Titel: Re:Eigene CPU
Beitrag von: Svenska am 06. December 2010, 16:48
...wobei im Intel-Museum ein Intel 4004 (4bit-Prozessor), aus einzelnen Transitoren gebaut, an der Wand hängt. Allerdings in sehr groß.
Titel: Re:Eigene CPU
Beitrag von: DeepDancer am 06. December 2010, 17:01
Im speziellen hab ich Interesse daran wie so ein Instructiondecode vor sich geht - und wie gesagt "hardwired"  :-D

Gibbets da was?

Edit:
Ach ich hab mir mal die vom Intel 4004 runtergeladen und werd mir das mal zu Gemüte führen - is glaub genau das was ich suchte erst mal :-)
Titel: Re:Eigene CPU
Beitrag von: erik.vikinger am 07. December 2010, 11:30
Hallo,


Im speziellen hab ich Interesse daran wie so ein Instructiondecode vor sich geht - und wie gesagt "hardwired"
So ein Decoder ist quasi ein switch-case in Transistoren gegossen. Oft generiert man einfach entsprechend viele Signale von denen dann nur eines auf TRUE stehen darf und welches die entsprechende Verarbeitungsschaltung aktiviert. Bei einer CISC-CPU wo die Befehle aus unterschiedlich langen Byte-Sequenzen bestehen können muss davor noch eine State-Maschine kommen die so lange alle Bytes einließt bis der Befehl komplett ist, gerade bei x86 mit den vielen optionalen Präfixen und Adressierungsmodi ist das eine ziemlich komplexe Angelegenheit. Ein kleiner RISC, wie z.B. MIPS, hat einen signifikant einfacheren Decoder der wirklich nur aus einem simplen Demultiplexer (http://de.wikipedia.org/wiki/Demultiplexer#Digitale_Demultiplexer) besteht.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Dimension am 03. January 2012, 22:07
Nachdem ich mir sämtliche Posts zum Thema durchgelesen habe möchte ich zuallererst meinen Respekt zum Ausdruck bringen. Die Konzepte sind insgesamt sehr durchdacht und innovativ. Es ist wirklich gut zu erfahren, dass es noch andere Personen gibt, die sich intensiv mit der Materie auseinandersetzen.

Ab Seite 2 hat sich mir allerdings die Frage aufgedrängt, welcher Umstand zur Diskussion über die Defragmentierung des physikalischen Speichers geführt hat. Hat Paging nicht ursprünglich die essentielle Aufgabe externe Fragmentierung zu unterbinden?

Früher oder später werde ich ebenfalls mit der Fragestellung konfrontiert werden, wie der physikalische Speicher verwaltet werden soll. Aus diesem Grund habe ich mir überlegt, ob es sinnvoll wäre, pro Page jeweils nur Datentypen gleicher Größe zuzuordnen. Sowohl die Belegung innerhalb der Pages, als auch die Zuweisung der Pages würden in Tabellen festgehalten werden. Bei adäquater Implementierung in Hardware könnte freier Speicher in amortisiert konstanter Zeit zugewiesen werden. Dies würde auch die Implementierung eines Garbage Collectors in Hardware begünstigen, da der Speicher bei kurzlebigen Allokationen (zB Lokale Objekte immerhalb einer Funktion) nicht fragmentiert wird.
Titel: Re: Eigene CPU
Beitrag von: kevin am 03. January 2012, 23:15
Erstmal vorneweg: Es heißt physischer Speicher, nicht physikalischer (auch wenn im Englischen beides zu physical wird).

Ich habe den Thread jetzt nicht nochmal durchgelesen, aber ich nehme an, dass der Kontext Eriks Architektur ist. Erik will sich den Overhead von Paging sparen, wenn möglich, und es daher den größten Teil der Zeit abgeschaltet lassen. Wenn man dann meistens nur noch Segmentierung zur Verfügung hat, um virtuellen Speicher zu implementieren, muss man das Fragmentierungsproblem natürlich anders lösen. Diesen Ansatz kann man für eine gute Idee halten, muss man aber nicht unbedingt. ;)

Und damit zu deiner Frage: Wenn du nicht irgendwas genauso abgefahrenes wie Erik machst, sondern ganz normal Paging benutzt, dann sehe ich keinen Grund für deine physische Speicherverwaltung, überhaupt irgendwelche Objekte, die kleiner als eine Page sind, zu verwalten. Im Prinzip sehe ich auf x86 nur drei Größen, mit denen umzugehen sinnvoll sein könnte: 4k, 2 MB, 1 GB - also genau die möglichen Pagegrößen. Die virtuelle Speicherverwaltung ist dafür zuständig, das alles richtig zu mappen, evtl. Sachen wie Copy on Write zu implementieren und ähnliches. Auch das ist alles auf Pageebene.

Und erst darauf aufbauend, also in der dritten Ebene, ergibt es überhaupt Sinn, sich mit Allokatoren zu beschäftigen, die beliebige Objektgrößen berücksichtigen. Üblicherweise hat man da ein malloc mit Freispeicherlisten, das beliebige Objektgrößen verwalten kann. Wenn man will, kann man dann aber auch Speicherpools für Objekte gleicher Größe benutzen, die dann einfacher sind und performanter arbeiten. Die Kosten dafür sind unter Umständen, dass insgesamt mehr ungenutzter Speicher in den Pools verschwendet wird.
Titel: Re: Eigene CPU
Beitrag von: Dimension am 04. January 2012, 01:53
Bei genauerem Hinsehen fiel mir auf, dass ich eine Seite vergessen hatte. Dort wurde Paging als Mittel gegen Fragmentierung ausführlich diskutiert.

Insgesamt hatte ich immer den Eindruck, dass sich sowohl Windows als auch Linux etwas schwer tun mit vielen kleineren malloc/free.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 04. January 2012, 03:48
Hallo,


für malloc/free ist nicht das OS zuständig sondern die libc innerhalb der Applikation, also Teil des User-Mode-Codes. Mit malloc/free wird der Heap benutzt und wie dieser intern verwaltet wird ist je nach Realisierung der libc ziemlich unterschiedlich. Da gibt es von ganz simplen verketteten Listen bis hin zu komplexen SLAB-Allocatoren eine ordentlich große Bandbreite ab Möglichkeiten. Letztlich bauen diese aber alle auf den normalen virtuellen Speicher auf den die Applikation vom OS bekommt, zumindest auf einem normalen Flat-Memory-System. In diesem Punkt unterschieden sich Windows und Linux (und auch alle anderen OSe in dieser Klasse) nur marginal von einander.

Was alle aktuellen OSe gemeinsam haben ist die Art mit der sie den virtuellen Speicher den Applikationen zur Verfügung stellen also das Paging, was eben der Grundstein des Flat-Memory-Konzepts darstellt. Paging ist zwar eine tolle Idee aber hat einen erheblichen Nachteil: es kostet bei jedem Speicherzugriff Performance. Das wird zwar mit Dingen wie dem TLB noch einigermaßen gut abgefangen aber spätestens wenn reale Programme mal wirklich ein paar TB an echtem Speicher benötigen und auch benutzen wollen dürfte das Konzept des TLB an seine Grenzen geraten sein und der Performanceverlust fürs Paging enorm werden. Deswegen möchte ich das mit Segmenten machen, die kosten deutlich weniger Performance und das ist auch nicht davon abhängig wie viele Segmente ein Programm insgesamt nutzt oder wie groß diese Segmente sind. Das einzigste was passen muss ist die Anzahl der Segment-Register (wovon x86 leider viel zu wenige hat so das dort Segmentierung nie wirklich zufriedenstellend war, nebst dessen das Segmentierung bei x86 auch nur schlecht im Befehlssatz integriert ist).

Für meine Heap-Implementierung, innerhalb der libc, will ich auch ein paar Segmente benutzen (die per Syscall vom OS geholt und verwaltet werden). Ich denke das ich es schaffe damit eine recht kleine innere Fragmentierung zu erreichen aber dafür habe ich das Risiko einer externen Fragmentierung also die einzelnen Segmente werden den physischen Speicher langsam fragmentieren (wie die Dateien auf einer Festplatte). Vor allem wenn die Speichernutzung sich langsam der Größe des physischen Speichers nähert wird das ein dringenderes Problem. Deswegen möchte ich die Möglichkeit haben für einzelne Segmente individuell Paging aktivieren zu können. Auf diese Art bleibt das Segment aus Sicht der Applikation intakt obwohl es über den physischen Speicher verstreut also fragmentiert ist. Das Paging ermöglicht es dann auch dieses Segment im Hintergrund, also ohne das die Applikation das merkt (vom Performanceverlust mal abgesehen), zu defragmentieren. Sobald das Defragmentieren der Segmente im physischen Speicher abgeschlossen ist kann das Paging wieder deaktiviert werden und die betreffende Applikation läuft wieder mit voller Performance.

Mir ist bewusst das ich mir damit einiges an Problemen aufhalse (z.B. was passiert wenn ein Segment vergrößert werden soll während es defragmentiert wird? gerade in einem SMP-System kann ja theoretisch beides gleichzeitig passieren) aber ich denke das sich das unterm Strich trotzdem lohnt. In den modernen Flat-Memory-OSen versucht man ja auch eine Art Defragmentierung einzubauen damit z.B. viele 4kB-Pages zu einer 2MB-Page zusammengefasst werden können, mit steigendem realen Speicherverbrauch der Applikationen wird das auch immer nötiger werden um den Performanceverlust durchs Paging in Grenzen zu halten.

Für mein System betrachte ich Paging nicht als Mittel gegen Fragmentierung, die wird bei meinen Segmenten zwangsläufig auftreten, sondern eher als Werkzeug um diese Fragmentierung wieder zu beheben. Auch fürs Swapping ist IMHO Paging die einzigst praktikable Lösung. Daneben eröffnet mir das Paging auch Dinge wie Memory-Mapped-File-IO aber das kommt erst sehr viel später dran. Im übrigen werden bei mir Segmente auch immer nur ganze Pages belegen und in jeder Page wird immer nur maximal ein Segment drin sein, so ähnlich wie ein Cluser bei FAT auch immer nur von einer Datei benutzt werden kann. Die Verwaltung des physischen Speichers in meinem OS wird also auch nur mit Pages arbeiten. Der wesentliche Unterschied zwischen Segmentierung und Paging ist der das für Segmentierung eine Basis und eine Größenangabe reicht um jedes Offset das innerhalb des Segments liegt in eine physische Adresse umrechnen zu können wogegen bei Paging riesige Tabellen erforderlich sind um eine virtuelle Adresse in eine physische Adresse umwandeln zu können.

In normalen Programmiersprachen, wie z.B. C, sind lokale Variablen (innerhalb von Funktionen) immer auf dem Stack und nicht auf dem Heap. Bei Java landet alles auf dem Heap, ist dort ein wesentlicher Aspekt des Gesamtkonzepts, und man kann als Programmierer auch nicht explizit zwischen Heap und Stack wählen aber bei vielen anderen Programmiersprachen wird zwischen Heap und Stack klar unterschieden und der Programmierer hat auch die Wahl was er benutzen möchte. Ich vermute Du solltest Dir einfach mal noch ein paar andere Dinge abseits von Java genauer ansehen um einen besseren Überblick über die vielfältigen Möglichkeiten zu bekommen.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: kevin am 04. January 2012, 12:17
Was meinst du mit "schwer tun"? Und auf jeden Fall ist es in beiden Fällen nicht der Kernel, der sich damit schwertut, sondern höchstens die libc.
Titel: Re: Eigene CPU
Beitrag von: Dimension am 04. January 2012, 15:58
Wenn ich mich richtig erinnere, weist libc Speicher ausschließlich im Vielfachen der Größe einer 4kb Page zu.
Titel: Re: Eigene CPU
Beitrag von: LittleFox am 04. January 2012, 16:32
Hi,

nein, die LibC ruft beim Betriebssystem ganze Pages ab (bei x86 meistens 4K). Verwaltet wird der Speicher in der LibC Byte-genau - wenn dein Objekt nur 21 Byte groß ist, wird auch nur so viel benötigt.

Grüße,
LittleFox
Titel: Re: Eigene CPU
Beitrag von: FreakyPenguin am 04. January 2012, 17:37
Ich würde ja mal sagen das hängt sehr stark von der konkreten Implementierung von malloc() ab. Eine gescheites malloc() wird sicher nicht nur ganze Pages rausgeben, aber aufgrund von Verwaltungsdatenstrukturen und allfälligem Padding wird warscheinlich mehr als nur 21 Byte benötigt.
Titel: Re: Eigene CPU
Beitrag von: LittleFox am 04. January 2012, 18:03
OK, bin jetzt vom Idealfall ausgegangen :D
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 04. January 2012, 18:58
Hallo,


OK, bin jetzt vom Idealfall ausgegangen :D
Auch dann wird für ein Objekt mit 21 Bytes sicher mehr als das benötigt, also Verwaltungsinformationen kommen in jedem Fall dazu.

Ansonsten fällt mir gerade ein das bei CPUs die keine unausgerichteten Speicherzugriffe unterstützen, was auf fast alle außer x86 zutrifft, der Pointer der von malloc kommt eigentlich zwangsläufig mindestens auf den größten nativ von der CPU unterstützten Datentyp ausgerichtet sein muss da ansonsten der Compiler gar keinen validen Code erzeugen kann. In einer Struktur werden auf solchen CPUs auch alle Member auf ihre Größe entsprechend ausgerichtet. Aber für x86 ist es auf jeden Fall möglich (wenn auch unperformant) komplett ohne Alignment und Padding auszukommen.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 11. January 2012, 18:36
Naja ich hab mir auch mal Gedanken bezüglich einer CPU (eigentlich mehrerer CPU's) gemacht. Zwar hab ich kein vollständiges Konzept entwickelt, aber folgende Ideen will ich dann dennoch der Allgemeinheit mitteilen.
Dabei habe ich mich hauptsächlich mit einem Problem beschäftigt, das bei RISC CPU's auftritt, nämlich die Befehlslänge.
Bei einer RISC Architektur stößt man immer auf folgende Probleme:
Wählt man die auf 32bit RISC Maschinen übliche Befehlslänge gleich der Wortlänge ist sie zu kurz um Konstanten und Adressen aller Art ohne großen Aufwand vernünftig laden zu können. (Auch wenn die z.B. bei ARM vorgestellten Möglichkeiten schon gut sind.) Würde man eine länger Befehlslänge nehmen (z.B. 64 bit) verschwendet man Speicher und braucht einen breiteren Bus oder doppelte Ladezyklen. Bei einer kürzeren Wortlänge (z.B. 16 bit) passt der Befehl selber, kaum in den Opcode.
Auf 64 Bit Architekturen verschärft sich das Problem. Verwendet man weiterhin 32 bit Befehlslänge kann der Befehl relativ gesehen nur halb so viele unmittelbare Informationen auf nehmen wie auf einem 32 bit Prozessor. Wält man eine Befehlslänge von 64 bit verbraucht das Programm unverhältnismäßig viel Arbeitsspeicher.
Analysiert man den Befehlssatz, erkennt man das es Befehle gibt, die mit wenig Speicher auskommen (Arithmetik, Flags setzten, Stack) und solche, die viel mehr Speicher brauchen.
Natürlich könnte man hingehen und grundsätzlich mit Variablen Befehlslängen arbeiten, aber das wäre nicht so Praktisch. Also habe ich versucht, nur mit Befehlen einfacher und doppelter Länge (32bit/64bit) zu arbeiten, ohne das Design zu sehr zu beeinträchtigen:

Möglichkeit 1:
In bestimmte Abständen, wird anstelle eines Befehls ein Datenwort geladen, das in einem speziellen Konstantenregister gespeichert wird, das man dann ganz normal auslesen und für seine Berechnungen verwenden kann. Der Prozessorbus wurde so erweitert, das er die Konstante mitläd. Die Informationen werden dann intern gepuffert. (z.B. sind die Befehle jeweils 32 bit lang und nach zwei Befehlen folgt ein 64 bit langes Datenwort. Der Prozessor besitzt einen 64 bit Bus, sodass er für jeden bearbeiteten Befehl 64 Bit einlesen kann.
Bei Befehlszyklus 1 ließt der Fetcher beide Befehle ein und gibt den 1. an den Dekoder und schreibt den 2. in den Puffer. Beim Befehlszyklus 2 liest der Fetcher das Datenwort in das Konstantenregister ein und übergibt den gepufferten Befehl an den Dekoder.

Möglichkeit 2:
Alle Befehlswörter haben doppelte Länge. Alle zwei Befehlszyklen ließt der Prozessor ein Befehlswort ein. An einem Steuerbit kann der Predekoder nun erkennen, ob es sich um einen langen Befehl, oder um zwei kurze Befehl handelt. Die interne Pipe hat immer eine größere Länge (z.B. 65 bit). Bei zwei kurzen Befehle werden diese auf Pipelange vergrößert und nacheinander in die Pipe geschoben. Bei einem langen Befehl, wird dieser gefolgt von einem "nop" Wort auf die Pipe geschoben.

Möglichkeit 3 (Die ich am besten finde):
Alle Befehlswörter haben einfache Länge. Es gibt aber ein Psydoregister, "Next Instruction Register" (nir), mit dem man den nächsten Befehl auf der Pipe ansprechen kann (oder den übernächsten Befehl). Der Lesezugriff erfolgt destruktiv, d.h. der Befehl wird beim lesen durch "nop" ersetzt, oder entsprechend markiert (das geht beim übernächsten Befehl einfacher, weil dieser noch nicht dekodiert wurde.) Der Schreibzugriff ist auch möglich wenn man dadurch den übernächsten Befehl anspricht, wodurch man eine Art selbst modifizierenden Code bauen kann.

Darüber hinaus bin ich dafür, Operatorlastige Befehle zu trennen, z.B favorisiere ich anstatt von konditionalen SprungBefehlen oder Bedingungspräfixe, lieber eine Kombination aus unbedingten Sprungbefehlen und einer "skipp next instruktion if <condition>" - Anweisung.

Eine Weiter Idee, die ich hatte, ist mal eine Stack basierende Architektur zu bauen oder eine Möglichkeit zu finden Registersicherung zu sparen. (Z. B. für einen eigenen Registersatz für den Taskshedule.)
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 13. January 2012, 21:59
Hallo,


Dabei habe ich mich hauptsächlich mit einem Problem beschäftigt, das bei RISC CPU's auftritt, nämlich die Befehlslänge.
Ja, das ist wohl wahr. Aber es gibt auch interessante Lösungen dafür, schau Dir mal den Itanium an. Auch das Problem mit der bedingten Ausführung ist dort auf sehr interessante Art und Weise gelöst.
(eigentlich könnte hier meine Antwort schon zu Ende sein aber ich hoffe man nimmt es mir bei diesem Thema nicht übel wenn ich das doch mit ein paar Buchstaben mehr erkläre)
Der Itanium benutzt 128 Bit große Befehls-Pakete und in jedem Paket sind 3 Befehle a 41 Bit (die restlichen 5 Bit dienen wimre der Verwaltung der Abhängigkeiten des aktuellen Pakets zu den vorhergehenden Paketen, das muss der Compiler einfügen damit Intel den Decoder etwas einfacher bauen kann) und jeder dieser Befehle kann bedingt ausgeführt werden. Einer dieser 3 Befehle (wimre nur der erste) kann ein Load-Immediate sein der volle 64 Bit in ein beliebiges Register lädt und der benutzt dann einfach den folgenden Befehl mit für die Daten und dieser folgende Befehl ist dann zwangsläufig ein NOP (ob das der Decoder so richtet oder ob das schon so drin sein muss weiß ich jetzt gar nicht mehr genau). Durch diese Pakete kann der Itanium immer 3 Befehle pro Takt decodieren und auch ausführen (eigentlich immer 6 weil alle real existierenden Itaniums immer gleich 2 Pakete pro Takt laden) und die 41 Bit bieten auch für alles wichtige ausreichend Platz (obwohl pro Operand 7 Bit benötigt werden da der Itanium 128 Register hat). Der Nachteil ist natürlich das der Code sehr groß ist (weswegen die Itaniums auch sehr riesige und teure Caches haben) und da die 3 Befehle in einem Paket immer gleichzeitig ausgeführt werden dürfen diese auch nicht von einander abhängig sein was den Compiler schon mal vor ziemliche Schwierigkeiten stellen kann (oft müssen dann nicht belegbare Plätze in den Paketen mit NOPs aufgefüllt werden so das die eh schon schlechte Befehlsdichte noch mehr leidet). Das Ergebnis spricht aber für sich, die Itaniums waren die schnellsten CPUs ihrer Zeit, vor allem in Relation zu ihrer gemächlichen Taktfrequenz. Zumindest für wissenschaftliche/mathematische Programme sind die Itaniums eigentlich eine perfekte Wahl, nur leider waren sie immer viel zu teuer und auch viel zu durstig.

(Auch wenn die z.B. bei ARM vorgestellten Möglichkeiten schon gut sind.)
Ja, der Weg von ARM ist interessant aber er hat auch einen eklatanten Nachteil, er erfordert das der Code immer auch lesbar ist (es werden ja ganz normale Speicherzugriffe nur eben mit IP-relativer Adressierung benutzt). Aus Gründen der Sicherheit ist es aber besser wenn Code nicht lesbar sondern nur ausführbar ist.

Ansonsten gibt es für Deine Version mit den 32Bit Befehlen noch eine Möglichkeit 4:
Um ein NOP zu kodieren sollten nicht mehr als 8 Bit nötig sein so das in jedem NOP immer 24 Bit an Daten enthalten sein können. Wenn Du nun einen Load-Immediate-Befehl baust der selber noch 16 Bits Daten enthält (die restlichen 16 Bits sollten doch reichen um den Befehl ansich und das Zielregister zu kodieren) dann könnte der im Decoder um 2 Takte verzögert werden (es käme eben für 2 Takte kein Befehl aus dem Decoder raus) und der Decoder würde intern aus diesem Befehl und den 2 folgenden NOPs insgesamt einen Load-Immediate mit vollen 64Bit Nutzdaten bauen. (wenn einer der beiden folgenden Befehle kein NOP ist gibt es eben eine Illegal-OpCode-Exception) Das hätte auch den Vorteil das nichts schlimmes passiert wenn mal einer der beiden Daten-NOPs angesprungen wird und auch der Disassembler (im Debugger) hätte keine Probleme was sinnvolles anzuzeigen. Ein weiterer Vorteil ist das diese Variante allein im Decoder passiert und Dein eigentlicher CPU-Kern davon nicht betroffen ist, spätestens wenn Du irgendwann doch mal das simple In-Order-Pipeline-Konzept verlassen möchtest wird es Dir sehr entgegen kommen Dich im CPU-Kern auf die eigentliche Arbeit konzentrieren zu können.


Das mit dem Skip-Befehl ist keine so schlechte Idee aber auch hier solltest Du das gleich im Decoder lösen und dem CPU-Kern nur Befehle übergeben bei denen die Ausführungsbedingung integraler Bestandteil ist. Es sollte auch möglich sein das sich dieses Condition-Präfix mit jedem Befehl kombinieren lässt, damit lassen sich wieder viele Sprünge vermeiden und das bringt einiges an Performance und spart oft auch Code. Im klassischen ARM-Befehlssatz ist auch jeder Befehl bedingt ausführbar aber das kostet bei ARM in jedem Befehl 4 Bit so das die ARM-Befehle nur 28 Bit wirklich zur Verfügung haben und das ist, trotz dessen das die Operanden nur 4 Bit benötigen, schon ziemlich knapp. Falls Du fürchtest mit nur einem Flag-Set irgendwann an Performance-Probleme zu stoßen, und das ist bei so einem Single-Point-of-Dependency sehr wahrscheinlich, solltest Du Dir mal anschauen wie das bei Alpha gelöst ist. Die haben zwar nur bedingte Sprünge (alle anderen Befehle werden immer unbedingt ausgeführt) aber dafür kann Alpha direkt ein beliebiges Register auf bestimmte Eigenschaften prüfen, z.B. == 0 oder != 0, und da es mehrere Register gibt kann zwischen dem Befehl der eine Bedingung ermittelt und dem zugehörigen bedingten Sprung-Befehl noch beliebiger anderer Code stehen solange der das eine bestimmte Register nicht verändert. Das sorgt dafür das der bedingte Sprung nicht erst warten muss bis der unmittelbar vorangegangene Test-Befehl durch die Pipeline gekommen ist sondern weil das eben oft schon einige Befehle her ist gibt es weniger Pipeline-Stalls.


Auch ich habe mir über die beiden Punkte (Befehlsgröße und bedingte Ausführung) einige Gedanken gemacht und (wie ich meine) auch ein paar interessante Lösungsmöglichkeiten gefunden, das ist ganz am Anfang dieses Threads recht gut beschrieben.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 14. January 2012, 17:29
Hallo,


Dabei habe ich mich hauptsächlich mit einem Problem beschäftigt, das bei RISC CPU's auftritt, nämlich die Befehlslänge.
Ja, das ist wohl wahr. Aber es gibt auch interessante Lösungen dafür, schau Dir mal den Itanium an. Auch das Problem mit der bedingten Ausführung ist dort auf sehr interessante Art und Weise gelöst.
(eigentlich könnte hier meine Antwort schon zu Ende sein aber ich hoffe man nimmt es mir bei diesem Thema nicht übel wenn ich das doch mit ein paar Buchstaben mehr erkläre)[...]
Naja, ganz grob entspricht das meiner Möglickeit 2. Allerdings halte ich so einen Itanium-Befehlssatz für überflüssig, weil diese Art von Parallelität nur selten vorkommt (entweder will man SIMD oder MISD.) Für SIMD ist der Befehlssatz zu mächig und damit zu teuer (Man nutzt die Möglichkeiten nicht richtig aus). MISD lässt sich, da Abhängigkeiten zwischen den Paralellbefehlen verbotenen sind, nicht schnelle als bei einem konservativen Befehlssatz lösen. (Ein MISD Prozessor wäre auch mal interessant.)
Zitat

(Auch wenn die z.B. bei ARM vorgestellten Möglichkeiten schon gut sind.)
Ja, der Weg von ARM ist interessant aber er hat auch einen eklatanten Nachteil, er erfordert das der Code immer auch lesbar ist (es werden ja ganz normale Speicherzugriffe nur eben mit IP-relativer Adressierung benutzt). Aus Gründen der Sicherheit ist es aber besser wenn Code nicht lesbar sondern nur ausführbar ist.

Ansonsten gibt es für Deine Version mit den 32Bit Befehlen noch eine Möglichkeit 4:
Um ein NOP zu kodieren sollten nicht mehr als 8 Bit nötig sein so das in jedem NOP immer 24 Bit an Daten enthalten sein können. Wenn Du nun einen Load-Immediate-Befehl baust der selber noch 16 Bits Daten enthält (die restlichen 16 Bits sollten doch reichen um den Befehl ansich und das Zielregister zu kodieren) dann könnte der im Decoder um 2 Takte verzögert werden (es käme eben für 2 Takte kein Befehl aus dem Decoder raus) und der Decoder würde intern aus diesem Befehl und den 2 folgenden NOPs insgesamt einen Load-Immediate mit vollen 64Bit Nutzdaten bauen. (wenn einer der beiden folgenden Befehle kein NOP ist gibt es eben eine Illegal-OpCode-Exception) Das hätte auch den Vorteil das nichts schlimmes passiert wenn mal einer der beiden Daten-NOPs angesprungen wird und auch der Disassembler (im Debugger) hätte keine Probleme was sinnvolles anzuzeigen. Ein weiterer Vorteil ist das diese Variante allein im Decoder passiert und Dein eigentlicher CPU-Kern davon nicht betroffen ist, spätestens wenn Du irgendwann doch mal das simple In-Order-Pipeline-Konzept verlassen möchtest wird es Dir sehr entgegen kommen Dich im CPU-Kern auf die eigentliche Arbeit konzentrieren zu können.
Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert. Bisher hab ich mich außerdem nie mit Befehlen die mit doppelter Datenabhängigkeit funktionieren beschäftigt. Die von dir beschriebene Möglichkeit hatte ich auch schon mal, da sie z.B. bei ARM schon vorbereitet wurde. Die never Kondition entspricht quasi deinem nop. Ein weiterer Nachteil deines Vorschlags ist, das man spezielle Befehle hierfür benötigt. Möglichkeit 3 zeichnete sich ja gerade dadurch aus, das man die Immideate wie ein Register nutzten konnte, weil die Daten allein über einen Registerzugriff aktiviert werden. Bei Dissassemblen hab ich eigentlich keine Schwierigkeiten. Der Dissassembler dekodiert dann halt so was wie add r1, r2, nir(0xABCDEF01) und setzt den abhängigen Befehl in Klammern, oder so.
Zitat
Das mit dem Skip-Befehl ist keine so schlechte Idee aber auch hier solltest Du das gleich im Decoder lösen und dem CPU-Kern nur Befehle übergeben bei denen die Ausführungsbedingung integraler Bestandteil ist. Es sollte auch möglich sein das sich dieses Condition-Präfix mit jedem Befehl kombinieren lässt, damit lassen sich wieder viele Sprünge vermeiden und das bringt einiges an Performance und spart oft auch Code. [...]
Das mit der universellen Zuordnung hatte ich eh so vorgesehen (es hieß ja nicht skip next branch). Überhaupt hab ich mir überlegt, ob ich nicht zwei Dekodierstufen einfügen soll. Das macht die Sache einfacher.
Zitat
... solltest Du Dir mal anschauen wie das bei Alpha gelöst ist.[...]
Die Alpha Methode wäre natürlich auch ne Möglichkeit, allerdings ist die Nutzung eines beliebigen Register natürlich mit viel notwendiger Information verbunden. Dafür ist diese Möglichkeit ziemlich nah an dem System, das auch viele Programmiersprachen anwenden. (von FreeBasic weiß ich, das ein if ausgeführt wir, wenn der Ausdruck ungleich null ist. In C ist es glaub ich genauso.)

Grundsätzlich gibt es drei Konditionsmöglichkeiten.
A) Keine Konditionellen Befehle außer bei Sprüngen. Pro. Keinen Speicherbedarf im Opcode  Kontra: Piplineunfreundlich
B) ARM-artige Konditionen: Pro: mächtig Kontra: Wenig Erweiterungsmöglichkeiten, Koste viel Speicher im Opcode
C) Alpha-artige Konditionen: Pro: Flexible Konditionsverwaltung Kontra: Externen Vergleichsoperatoren nötig. Koste viel Speicher im Opcode und Register
D) if TRUE Flag set Kondition: Pro: Wenig Speicher im Opcode Kontra: Externe Unterstützung nötig.

Ich persönlich könnte mir also eine Möglichkeit vorstellen, bei der der einzelne Befehl keine Kondition trägt. Sprünge hätten dann sowas wie eine erweiterte Alpha-artige Kondition und Skip-Präfixe ein leistungsfähiges Konditionssystem.

Darüber hinaus gibt es natürlich noch Möglichkeiten, den Aufbau zu erweitern, indem man konditionelle Sprungbefehle oder Befehlspräfixe einführt.
Zitat
Auch ich habe mir über die beiden Punkte (Befehlsgröße und bedingte Ausführung) einige Gedanken gemacht und (wie ich meine) auch ein paar interessante Lösungsmöglichkeiten gefunden, das ist ganz am Anfang dieses Threads recht gut beschrieben.
Du weißt aber schon, das du den Decoder damit ziemlich auf blähst, da du eigentlich 3 Instructionsets verwendest. Ich würde den Dekoder in verschiedene Teile aufteilen. Du brauchtest also 1 Meta-Decoder (Trennt das Pakete auf) + 2 60bit-Decoder + 3 42bit-Decoder (39bit+3bit Condition) + 6 20bit Decoder = 12 Teildecoder. Da eigentlich alle Teildecoder das gleiche machen, hättest du ziemlich viele Einzellschaltungen. (zum Vergleich ARM brauch für seine Schaltung nur ein ARM und einen Thumb decoder.) Ich würde mir deshalb überlegen, ob du nicht die kurze Befehle auf den langen Abbilden kannst, z.B. indem du konstante Bits einfügst. Außdem würde ich mich fragen, ob du wirklich 6 20bit Anweisungen simultan ausführen musst, oder ob es reicht, immer nur 3 Befehle gleichzeitig zu bearbeiten und die Pipline entsprechend zu modifizieren, oder die Piplinegeschwindigkeit zu halbieren.

Darüber hinaus sollte dir klar sein, das es schwierig ist für einen VLIW Befehlssatz Code zu schreiben, weil das Design nicht der traditionellen Logik entspricht, weshalb das Compiler Backend ziemlich kompliziert wird. Assemblerprogrammierung für eine solche Plattform ist sogar noch schwieriger. Auch die interne Logik wird aufgebläht, weil viele Baugruppen mehrfach auftreten. Von der Theorie her, mag solch ein Befehlssatz ja interessant sein, in der Praxis implementiert man aber lieber SIMD Instruktionen.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 15. January 2012, 12:14
Hallo,


eines vorweg: in meiner CPU gibt es kein EPIC. Das empfinde ich persönlich als fürchterliche Fehlentscheidung von Intel, auch wenn es objektiv dafür wohl keinen Grund gibt da die Itaniums ja durchaus recht schnell sind. In kleinen DSPs mit internem RAM macht EPIC eventuell noch Sinn da hier alle Befehle, auch die Speicherzugriffe, mit kurzer und bekannter Latenz auskommen aber für eine General-Purpose-CPU, wo einzelne Befehle auch mal unvorhersehbar lange dauern können, ist EPIC meiner persönlichen Meinung nach absolut fehl am Platz. Hier ist Out-of-Order-Execution deutlich sinnvoller. Weil im Itanium angestrebt wird das jedes Befehls-Paket für seine Ausführung in der Pipeline immer nur einen Takt benötigt sind da einige ziemlich krasse Design-Entscheidungen getroffen wurden. Es gibt da z.B. keine klassischen Multi-Takt-Befehle wie die Division (bei Integer und Floating-Point) oder überhaupt komplexe mathematische Befehle (die müssen alle händisch vom Compiler gebaut werden), dafür gibt es jeweils spezielle Befehle die die einzelnen Iterationsschritte der benötigten Algorithmen (die normalerweise eine HW-State-Maschine selbstständig erledigt) anbieten und der Compiler muss dann damit zurecht kommen. Der Itanium arbeitet rein In-Order mit einer ziemlich kurzen Pipeline, von daher würde er auch alles blockieren wenn er Befehle hätte die mehrere Takte brauchen. Bei der Division verkauft Intel das sogar als Vorteil weil der Compiler bei der Zusammenstellung der händischen Division zwischen möglichst kurzer Latenz für eine Division und möglichst hohem Durchsatz für viele Divisionen wählen kann. Ja, das ist durchaus ein nettes Feature aber es macht den Compiler ziemlich komplex und langsam und es gibt auch etliche Situationen wo der Compiler diese Entscheidung gar nicht wirklich treffen kann weil er einfach nicht weiß wie sich das Gesamtsystem zur Laufzeit verhält. Bei den Speicherzugriffen arbeitet der Itanium exzessiv mit Prefetch-Befehlen die der Compiler passend einflechten muss, damit wird dem Cache-Controller schon mal mitgeteilt was als nächstes benötigt wird so das schon mal vorgesorgt werden kann damit dann wenn der eigentliche Speicherzugriff passiert dieser auch in möglichst einem Takt direkt vom Cache bedient werden kann. Das Problem ist dass das nicht immer gut funktioniert (es gibt ja z.B. auch Speicherbereiche die gar nicht cachebar sind wie HW-MMIO oder Algorithmen deren als nächstes benötigten Daten nicht so einfach vorhersehbar sind) und der Cache-Controller dadurch auch ziemlich komplex wird da er mit vielen parallelen Prefetch-Jobs umgehen können muss.
Alles in allem muss man sagen das Intel mit dem Itanium eine absolut einzigartige CPU geschaffen hat, etwas wirklich vergleichbares gibt es meines Wissens nach nicht auf diesem Planeten. Das trotz all der potentiellen Schwierigkeiten, die bei allen anderen EPIC-Designs auch voll zuschlagen oder auf Grund bestimmter Randbedingen nicht auftreten die dann aber keine General-Purpose-CPU mehr sind, eine so leistungsfähige CPU bei raus gekommen ist bleibt allein eine Leistung der guten Ingeniöre bei Intel die eben für jedes dieser Probleme eine gute Lösung gefunden haben. Unterm Strich ist damit der Itanium zwar extrem leistungsfähig aber die vielen Extras die Intel braucht um das auch tatsächlich mit EPIC zu schaffen machen den Itanium einfach unwirtschaftlich. Ich hab selber einige Zeit überlegt ob man mit diesem Konzept was ansprechendes erreichen kann bin aber zu dem Schluss gelangt das ich lieber auf eine klassische Out-of-Order-Execution einer seriellen Befehlsfolge setzen möchte. Deswegen benutze ich zwar eine Art VLIW-Kodierung, um eine möglichst hohe Code-Dichte bei trotzdem sehr leistungsfähigen Befehlen zu erreichen, aber jeder Befehl ist für sich eigenständig und wird, aus Sicht des Programmierers/Compilers, seriell abgearbeitet. Trotzdem sollen die Abhängigkeiten zwischen den Befehlen natürlich möglichst klein sein (was bei 60 frei nutzbaren Registern und 4 unabhängigen Flag-Sets auch kein so großes Problem sein sollte) damit der CPU-Kern bei der Befehlsausführung ein möglichst hohes Maß an Instruction-Level-Parallelism schafft und eben auch ein Out-of-Order-Commit der Befehle kein Problem darstellt. Mein Decoder wird mit Sicherheit sehr viel Logik im FPGA belegen, vermutlich mehr als alle ALUs der Verarbeitungspipeline zusammen, aber er wird einen seriellen Strom an (einheitlichen) Befehlen liefern und man wird es den einzelnen Befehlen dann nicht mehr ansehen ob sie mit 20 Bit, 39 Bit oder 60 Bit codiert waren. Ich möchte es erreichen das der Decoder pro Takt 2 bis 6 dekodierte Befehle liefern kann (eben ein komplettes Paket) um für die eigentliche Verarbeitung immer genügend Nachschub zu liefern.


Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert.
Hä, was meinst Du? Denkst Du das ein NOP mit nur 8 Bits zu kodieren Verschwendung ist?

Möglichkeit 3 zeichnete sich ja gerade dadurch aus, das man die Immideate wie ein Register nutzten konnte, weil die Daten allein über einen Registerzugriff aktiviert werden.
Ja, aber wenn Du das erst bei der tatsächlichen Ausführung der Befehle machst verhinderst Du damit effektiv jegliche Form von Instruction-Level-Parallelism weil Du den (über)nächsten Befehl erst dann dekodieren kannst wenn der aktuelle Befehl abgearbeitet wird. Ich kann Dir nur dringlichst empfehlen zu versuchen dafür eine Lösung zu finden die nur im Decoder alleine passiert damit Du Dir später nichts verbaust.

Die Alpha Methode wäre natürlich auch ne Möglichkeit, allerdings ist die Nutzung eines beliebigen Register natürlich mit viel notwendiger Information verbunden.
Ja, es wird für die Bedingung nicht nur eine Register-Nummer (die bei Alpha 5 Bit groß ist) benötigt sondern auch die Bedingung als solches (da gibt es auch > 0 oder <= 0 jeweils nach signed und unsigned getrennt, wimre gibt es das bei Alpha auch für Floating-Point) und deswegen gibt es bei Alpha wohl nur bedingte Sprünge und sonst nichts. Wenn man diese Bedingung aber in ein eigenständiges Präfix auslagert, das sich mit jedem Befehl kombinieren lässt, dann hat man ja eigentlich auch genügend Bits zur Verfügung um hier auf möglichst große Flexibilität zu setzen. Wenn das Präfix 32 Bits groß ist könnte man sogar die eigentliche Vergleichsoperation (SUB oder AND) und 2 Register mit hinein kodieren. Aber das bläht die interne Befehlsdarstellung ziemlich auf. Ich benötige 7 Bits für die Bedingung (2 Bits für das benutze Flag-Set und 5 Bits für die eigentliche Bedingung, da sind zum einen die 14 Klassiker und noch einige Floating-Point-Bedingungen) und das auch hinter dem Decoder bei der internen Befehlsdarstellung, was IMHO einen brauchbar guten Kompromiss darstellt.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 15. January 2012, 18:35
Naja, dein Konzept hört sich schon mal gut an. Das mit dem Fehlen der Division gibt es bei mehren RISC Architekturen. Das hat mit dem entfernen des Microcodes zu tuen der Microcode kann auch wie bei den moderen x86-32 ein interner RISC Code sein. Die Division muss ist ein sehr komplexer Ablauf, der in der Regel durch Microcode gelöst werden muss. Und da EPIC auf RISC basiert und deshalb keinen Mikrocode beherrscht, ist die Implementierung der Division ziemlich aufwändig (und wird auch vergleichsweise selten gebraucht.)

Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert.
Hä, was meinst Du? Denkst Du das ein NOP mit nur 8 Bits zu kodieren Verschwendung ist?
Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg. Halte ich es für nicht so praktisch wenn ich ein 256tel der möglichen Instructionswörter für nop's hergebe und dann für viele, zusätzliche Befehle, die dann auch noch Platz für die restlichen Bits reservieren müssen, find ich Problematisch. (Natürlich könnt man einen Bareal Shifter verwende und den shift 0 Funktionen, eine Spezialfunktion zuweisen.

Ja, aber wenn Du das erst bei der tatsächlichen Ausführung der Befehle machst verhinderst Du damit effektiv jegliche Form von Instruction-Level-Parallelism weil Du den (über)nächsten Befehl erst dann dekodieren kannst wenn der aktuelle Befehl abgearbeitet wird. Ich kann Dir nur dringlichst empfehlen zu versuchen dafür eine Lösung zu finden die nur im Decoder alleine passiert damit Du Dir später nichts verbaust.
Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll. Jetzt baue ich einfach eine Flag ein, die die Ausführung des Befehls unterbindet. Du darfst nicht vergessen, das ich einen Befehl auch dann dekodieren kann, wenn ich ihn gar nicht ausführe. (Die Ausführschaltung ist einfach für einen Takt gesperrt). Umgekehrt weiß ich auch schon nach dem dekodieren, ob ich auf das NIR zugreifen werden, und deshalb den nächsten Befehl nicht ausführen darf.
Für den Schreibzugriff hast du recht. Da ich aber Schreibzugriff auf das NIR als ehr wenig gebrauchte Anweisung betrachte, braucht diese Funkion auch keine super effektive Ausführmethode. Das heißt, ich der Schreibzugriff würde halt die Pipline leeren, oder zumindest zurück schieben. Eine sehr lange Pipline halte ich sowieso für unpraktisch.
Ja, es wird für die Bedingung nicht nur eine Register-Nummer (die bei Alpha 5 Bit groß ist) benötigt sondern auch die Bedingung als solches (da gibt es auch > 0 oder <= 0 jeweils nach signed und unsigned getrennt, wimre gibt es das bei Alpha auch für Floating-Point) und deswegen gibt es bei Alpha wohl nur bedingte Sprünge und sonst nichts. Wenn man diese Bedingung aber in ein eigenständiges Präfix auslagert, das sich mit jedem Befehl kombinieren lässt, dann hat man ja eigentlich auch genügend Bits zur Verfügung um hier auf möglichst große Flexibilität zu setzen. Wenn das Präfix 32 Bits groß ist könnte man sogar die eigentliche Vergleichsoperation (SUB oder AND) und 2 Register mit hinein kodieren. Aber das bläht die interne Befehlsdarstellung ziemlich auf. Ich benötige 7 Bits für die Bedingung (2 Bits für das benutze Flag-Set und 5 Bits für die eigentliche Bedingung, da sind zum einen die 14 Klassiker und noch einige Floating-Point-Bedingungen) und das auch hinter dem Decoder bei der internen Befehlsdarstellung, was IMHO einen brauchbar guten Kompromiss darstellt.
Naja ich würde ohnehin nur zwei Konditionale Befehle verwenden: Den primitiven Sprung Befehl mit Registerhilfe (oder über NIR auch mit Immidiate.) und halt das Condition Präfix, sowie der evalFLAGS Befehl (setzt ein Register auf 0 oder -1, entsprechend der FLAGS). Da ich mit Nullregister arbeite, sind die bedingungsfindenden Opcodes nicht so speziell.
Im allgemeinen Sind folgende Optionen für eine Condition notwendig, die aber auch zusammengefasst werden.
1. Setzten der Flags (Arithmetische Befehle die als Rd das Nullregister verwenden.)
2. Evaluation der Bedingung in einen Wahrheitswert. evalFLAGS Befehl
3. Aktivierung der Bedingung (Registerorientierte Conditionspräfixe)
4. Aktion die unter der Bedingung ausgeführt wird.

Sprungbefehle fassen hierbei die Aktionen 2-4 oder 3-4 zusammen. Flagorientierte Konditionspräfixe fassen Aktion 2 und 3 zusammen. In beiden Fällen muss der Wahrheitswert dann nicht in notiert werden.
Im Instruction set, unterscheiden sich  evalFLAGS Befehl und Flagorientierte Konditionspräfixe dadurch, dass bei letzteren der Programm Counter als "Evaluationsziel angegeben wird."
Registerorientierte Conditionspräfixe gleichen den 3-4-Sprungbefehlen, nur das der PC als "Sprungziel" angegeben wird.
Beide Präfixe lassen sich deshalb leicht daran erkennen, das sie das PC adressieren.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 15. January 2012, 21:42
Hallo,


Das mit dem Fehlen der Division gibt es bei mehren RISC Architekturen.
Das hat aber nichts mit RISC oder Microcode zu tun, es geht einfach darum sich die Transistoren für die HW-Divisions-Schaltung einzusparen. Bei den CPUs wo es etwas mehr auf Rechenleistung ankommt (also eben nicht die typischen kleinen Micro-Controller) ist für gewöhnliche ein HW-Divisions-Schaltung drin, auch bei RISC. Die braucht halt einfach nur einige Takte bis das Ergebnis anliegt und solange steht eben die Pipe-Line wenn es keine Out-of-Order-Execution gibt, trotzdem ist diese Zeit immer noch deutlich kürzer als wenn der Compiler eine Division händisch baut (außer natürlich beim Itanium da der spezielle Befehle hat die direkt die einzelnen Iterationsschritte der Division anbieten, was wimre keine andere CPU hat). Auch bei der Verarbeitung von Floating-Point-Zahlen ist bei den meisten CPUs (so sie denn Floating-Point überhaupt können) nicht nur Addition/Subtraktion und Multiplikation in HW verfügbar (üblicherweise benötigen selbst diese 3 Befehle bereits mehrere Takte) sondern oft auch Division und Quadratwurzel (die dann noch mehr Takte benötigen, deswegen sind diese beiden auch beim Itanium nicht vorhanden (ebenso wie die noch komplexeren Operationen wie Logarithmus oder Sinus) sondern nur als Einzelschritte, es gibt beim Itanium also die passenden Rechenwerke aber die State-Machines die das üblicherweise ansteuern fehlen und da muss dann der Compiler nachhelfen).

Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg.
Okay, aber so ein Null-Register hab ich nicht und möchte ich eigentlich auch nicht, ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.

Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll.
Aber was ist wenn Du die Befehl nicht hintereinander sondern gleichzeitig ausführen möchtest? Wäre ja möglich das die alle voneinander unabhängig sind und daher diese Parallelität durchaus erlaubt ist. Ich bleibe vorerst bei der Meinung das Du Dir mit diesem Konzept die nächst höhere Performance-Stufe verbaust oder zumindest erheblich komplizierter machst. IMHO ist es gerade einer der Vorteile wenn man große Befehle hat die komplette Immediates aufnehmen können das der Decoder alle Befehle unabhängig voneinander dekodieren und die Verarbeitungs-Logik diese auch unabhängig voneinander ausführen kann, natürlich soweit keine Datenabhängigkeiten o.ä. vorliegen aber das zu ermitteln ist schon komplex genug so das ich persönlich mir da nicht auch noch so ein NIR aufbürden würde.

sowie der evalFLAGS Befehl (setzt ein Register auf 0 oder -1, entsprechend der FLAGS).
Also für so einen Befehl sehe ich keine Verwendungsmöglichkeit. Wenn Du dann dieses Register mit einem folgenden bedingten Befehl prüfen möchtest so könntest Du auch gleich die Flags prüfen und würdest Dir einen Schritt sparen und wenn es Dir um das bedingte Laden von Immediates geht um diese dann einheitlich weiter zu verarbeiten dann benutze doch gleich einen bedingten Load-Immediate-Befehl der die Flags prüft.

Setzten der Flags (Arithmetische Befehle die als Rd das Nullregister verwenden.)
Heißt dass das Rechenbefehle die nicht aufs Null-Register schreiben keine Flags ablegen können? Das würde ich als sehr ungeschickt betrachten da es durchaus viele Fälle gibt wo man das Ergebnis haben will und auch die Flags interessieren.

Sprungbefehle fassen hierbei die Aktionen 2-4 oder 3-4 zusammen. Flagorientierte Konditionspräfixe fassen Aktion 2 und 3 zusammen. In beiden Fällen muss der Wahrheitswert dann nicht in notiert werden.
Im Instruction set, unterscheiden sich  evalFLAGS Befehl und Flagorientierte Konditionspräfixe dadurch, dass bei letzteren der Programm Counter als "Evaluationsziel angegeben wird."
Registerorientierte Conditionspräfixe gleichen den 3-4-Sprungbefehlen, nur das der PC als "Sprungziel" angegeben wird.
Beide Präfixe lassen sich deshalb leicht daran erkennen, das sie das PC adressieren.
Um ehrlich zu sein habe ich das überhaupt nicht verstanden, könntest Du noch mal anders beschreiben wie Du bedingte Befehlsausführung realisieren möchtest?


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 17. January 2012, 21:13
Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg.
Okay, aber so ein Null-Register hab ich nicht und möchte ich eigentlich auch nicht, ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.
Naja, so schwer schätz ich den bei 32/64 Registern dann nicht ein. Dafür macht so ein Register viele Sachen einfach einfacher. Man braucht z.B. keine extra Compare Befehle und kann sich auch Sachen wie den mov Befehl sparen. Ohne Null Reg würde der Opcode dann halt einfach "mov anyGPReg1, anyGPReg1" heißen. Ich mein, ich hab mir ja nur ein paar Gedanken gemacht. Noch kein greifbares Design.

Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll.
Aber was ist wenn Du die Befehl nicht hintereinander sondern gleichzeitig ausführen möchtest? Wäre ja möglich das die alle voneinander unabhängig sind und daher diese Parallelität durchaus erlaubt ist. Ich bleibe vorerst bei der Meinung das Du Dir mit diesem Konzept die nächst höhere Performance-Stufe verbaust oder zumindest erheblich komplizierter machst. IMHO ist es gerade einer der Vorteile wenn man große Befehle hat die komplette Immediates aufnehmen können das der Decoder alle Befehle unabhängig voneinander dekodieren und die Verarbeitungs-Logik diese auch unabhängig voneinander ausführen kann, natürlich soweit keine Datenabhängigkeiten o.ä. vorliegen aber das zu ermitteln ist schon komplex genug so das ich persönlich mir da nicht auch noch so ein NIR aufbürden würde.

Man könnte auch unsere beide Stategien kombinieren. Ein Opcodepaket besteht aus meheren Opcodes, die verschiedene Länge annehmen können. Beide Opcodelängen sind identisch, allerdings sind bei den kurzen Opcodes Teile derselbigen abgeschnitten. So enthalten kurze Opcodes zwar vollwertigen Befehl, aber kein Immediate Sektor (der impizit 0 enthält.) Lange Opcodes enthalten eine Immediate, die sich über ein Psydoregister (IR immidete Register, remake von NIR) ansprechen lässt.) Damit würde man variable Opcodelängen ermöglichen und den Decoder einfach halten.

Setzten der Flags (Arithmetische Befehle die als Rd das Nullregister verwenden.)
Heißt dass das Rechenbefehle die nicht aufs Null-Register schreiben keine Flags ablegen können? Das würde ich als sehr ungeschickt betrachten da es durchaus viele Fälle gibt wo man das Ergebnis haben will und auch die Flags interessieren.
Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.


Sprungbefehle fassen hierbei die Aktionen 2-4 oder 3-4 zusammen. Flagorientierte Konditionspräfixe fassen Aktion 2 und 3 zusammen. In beiden Fällen muss der Wahrheitswert dann nicht in notiert werden.
Im Instruction set, unterscheiden sich  evalFLAGS Befehl und Flagorientierte Konditionspräfixe dadurch, dass bei letzteren der Programm Counter als "Evaluationsziel angegeben wird."
Registerorientierte Conditionspräfixe gleichen den 3-4-Sprungbefehlen, nur das der PC als "Sprungziel" angegeben wird.
Beide Präfixe lassen sich deshalb leicht daran erkennen, das sie das PC adressieren.
Um ehrlich zu sein habe ich das überhaupt nicht verstanden, könntest Du noch mal anders beschreiben wie Du bedingte Befehlsausführung realisieren möchtest?
Naja, das war eh so ein spontaner Einfall, den ich vermutlich nicht umsetzten würde.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 18. January 2012, 10:07
Hallo,


... ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.
Naja, so schwer schätz ich den bei 32/64 Registern dann nicht ein. Dafür macht so ein Register viele Sachen einfach einfacher.
Naja, das fehlende Register kostet sicher etwas Performance wogegen die Art und Weise wie die Befehle codiert sind erst mal keine Performance kostet, solange diese Codierung nicht so ineffizient ist das die Befehle deutlich größer werden.

Einen CMP-Befehl habe ich auch nicht (ebenso wie ich keinen TEST-Befehl habe) sondern dafür wird ein ganz normaler SUB-Befehl (oder AND-Befehl) benutzt und das Ergebnis einfach nicht verwendet. Aus Sicht der CMP/TEST-Operation ist es egal ob das Ziel-Register grundsätzlich keine Werte speichern kann oder ob der Wert einfach nur ignoriert wird aber für andere Situationen ist ein Register weniger eben eine Register weniger und das kann manchmal etwas Performance kosten. Auf ein MOV würde ich auch grundsätzlich nicht verzichten, klar kann man das mit "ADD RD,RS,R0" ersetzen aber hier würde entweder eine sinnlose Addition passieren obwohl der Wert der in RD rein soll schon einen Takt eher bekannt ist oder Dein Decoder erkennt das und erzeugt einen simplen MOV-Befehl für die Pipeline die dann natürlich trotzdem einen dedizierten MOV-Befehl unterstützen muss und dann ist es IMHO auch nicht mehr schlimm wenn der MOV auch im normalen OpCode-Format enthalten ist. Ich persönlich halte nicht viel davon wenn die Befehle je nach Parameter sich unterschiedlich verhalten, beim klassischen ARM-Befehlssatz ist es ja so das Befehle die R15 modifizieren und die Flags setzen sich anders verhalten und einen Modus-Wechsel durchführen können. Mag sein dass das minimal kompakteren Code ergibt als wenn man diese Operationen mit eigenen Befehlen ermöglicht aber innerhalb der CPU (u.a. beim der internen Befehlsdarstellung) erspart man sich eher nichts da ja trotzdem alle Funktionalitäten unterstützt werden müssen, das macht IMHO nur den Decoder komplizierter.

Man könnte auch unsere beide Stategien kombinieren. Ein Opcodepaket besteht aus meheren Opcodes, die verschiedene Länge annehmen können. Beide Opcodelängen sind identisch, allerdings sind bei den kurzen Opcodes Teile derselbigen abgeschnitten. So enthalten kurze Opcodes zwar vollwertigen Befehl, aber kein Immediate Sektor (der impizit 0 enthält.) Lange Opcodes enthalten eine Immediate, die sich über ein Psydoregister (IR immidete Register, remake von NIR) ansprechen lässt.) Damit würde man variable Opcodelängen ermöglichen und den Decoder einfach halten.
Ja, das klingt interessant. Machen Sie es so! ;)

Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.
Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?

das war eh so ein spontaner Einfall
Dafür ist dieser Thread ja da. Von dem was ich am Anfang dieses Threads (immerhin vor knapp 2 Jahren) geschrieben habe ist heute auch nicht mehr alles gültig, Konzepte entwickeln sich eben auch mal weiter.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 18. January 2012, 19:54
Einen CMP-Befehl habe ich auch nicht (ebenso wie ich keinen TEST-Befehl habe) sondern dafür wird ein ganz normaler SUB-Befehl (oder AND-Befehl) benutzt und das Ergebnis einfach nicht verwendet.
Ich hab mir überlegt, das 0reg doch abzuschaffen. Für die nicht vorhandenen TEST/COMP befehle, kann man ja die Daten in das IR schreiben, wo sie quasi weg sind. (Für den Executer ist das IR ein ganz normales Register. Der einzige Unterschied ist, das es extern durch den Pipe Shift geladen werden kann.)
Auf ein MOV würde ich auch grundsätzlich nicht verzichten, klar kann man das mit "ADD RD,RS,R0"
Da ich mit IR kein echtes 0-Register mehr habe, brauch ich natürlich auch einen echten MOV Befehl.

Generell bin ich auch der Meinung, das es bei Befehlen nicht zu viele Ausnahmen geben sollte. Auf der anderen Seite muss ein Schreibzugriff auf den PC natürlich registriert werden.
Ein Problem ist das Verhältnis von PC Addresse und Instruktion Fetch Addresse. Ich persönlich finde, dass man da einen Unterschied machen muss. Das der PC auf die Fetch-Addresse zeigt, halte ich für dumm, weil man so die Piplinelänge festlegt, es dem Programmierer schwieriger macht und zum anderen trotzdem umrechne muss (z.B. beim Sprung zu einem Unterprogramm, ist aber nicht so häufig wie andere Sprünge). Ich löse das deshalb so, das der PC auf den Befehl zeigt, der gerade ausgeführt werden soll. Der Fetcher besitzt dann eine Schaltung, die die Piplinelänge entsprechend addiert. Wichtig ist das ganze bei einem Pipline-Flush, bei dem dann der PC entsprechend dekrementiert wird. (Sodass er dann vor dem eigentlichen Sprungziel liegt. Für beides gibt es natürlich spezielle Schaltungen, die auf diese Funktion optimiert worden sind).

Meine Pipe sieht wie folgt aus:

Fetcher -(Packet)-> Puffer -> Unpacker -(Langer Befehl 60/124 bit) -┬-> Decoder --> Executer (--> Write Back)
                                                                alternativer Fetcher -┘

Eine weitere wichtige Funktion ist der sogenannte alternative Fetcher. Ebenso wie der normale Fetcher, kann auch diese Baugruppe Befehle in die Pipline einlesen, allerdings stehen diese nicht an irgendeiner RAM Adresse, sondern von einem externen oder auch interenen Bauelement. (In der Regel sind einige Funktionen intern vorprogrammiert. z.B. die Division.) Als Faustregel gilt. Negative Integer werden extern ausgeführt, positive Intern. Exteren Befehle werden über einen Port angesteuert. Wichtig beim alternativen Fetcher ist, das es keine Adressen gibt, und dass der PC auch (meist nicht) nicht hochgezählt wird. Schleifen und Sprünge erreicht man einfach durch rekursive Aufrufe oder durch interne Steuerung des alternativen Fetchers.

Man könnte auch unsere beide Stategien kombinieren. Ein Opcodepaket besteht aus meheren Opcodes, die verschiedene Länge annehmen können. Beide Opcodelängen sind identisch, allerdings sind bei den kurzen Opcodes Teile derselbigen abgeschnitten. So enthalten kurze Opcodes zwar vollwertigen Befehl, aber kein Immediate Sektor (der impizit 0 enthält.) Lange Opcodes enthalten eine Immediate, die sich über ein Psydoregister (IR immidete Register, remake von NIR) ansprechen lässt.) Damit würde man variable Opcodelängen ermöglichen und den Decoder einfach halten.
Ja, das klingt interessant. Machen Sie es so! ;)
Ich denke aber ich werde ehr nicht so viele Befehlslängen anbieten. (Es gibt einen 64 bit Doppelpaket, das entweder einen 60 Bit Befehl, 3 20 Bit Befehle oder nur 1 20 bit Befehl + 40 bit Zusatzinfos enthalten kann. (Die dann die Pipe abkürzen.)

Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.
Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?
Ja so umgefähr.

Allerdings wollte ich doch eigentlich eine Programmiersprache und keine CPU bauen.
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 18. January 2012, 20:18
Vergiss das mit meinen Opcodelängen.

Jeder Befehlsblock besteht aus zwei Befehlspacketen.
Diese enthalten
4 bit Verwaltungsdaten+ 60bit Infos hierbei gilt:
Verwaltungsdaten:
0000b = 2. Befehlspaket einer Folge.
andere.
Kombination aus 20 bit, 40 bit und 80 bit Befehlen.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 19. January 2012, 20:53
Hallo,


Auf der anderen Seite muss ein Schreibzugriff auf den PC natürlich registriert werden.
So wie ich mir das denke ist der PC (der bei mir einfach nur R63 ist und damit von jeden Befehl gelesen und beschrieben werden kann) zwar logisch ein normales Register aber nicht physisch. Bei Lesezugriffen auf dieses Register wird auch einfach immer das Offset des entsprechenden Befehls geliefert (es ist gerade bei positionsunabhängigen Code wichtig das der sich auf ein bestimmtes Verhalten beim Lesen des PC verlassen kann) und Schreibzugriffe auf R63 werden nicht vom normalen Register-File entgegengenommen sondern speziell abgefangen (aber außerhalb der ALUs) und dem Dispatcher zugeleitet damit der weiß von wo er als nächstes Befehle holen soll. Für die eigentlichen Ausführungseinheiten ist R63 ein ganz normales Register wie alle anderen auch. Es wird aber trotzdem einige Operationen geben die nicht mit R63 arbeiten können aber die lassen sich alle im Decoder abfangen und machen in der Pipeline keinen Stress.

Meine Pipe sieht wie folgt aus:
Also quasi der Klassiker. Aber die erlaubt Dir natürlich keine Parallelität zwischen den einzelnen Befehlen und auch keine Out-of-Order-Exection.
Wenn ich Dich richtig verstanden habe möchtest Du z.B. die Division "micro-codiert" erledigen (indem Du anstatt einem Befehl viele Befehle in die Pipeline lässt die diesen Job dann mit vielen Schritten erledigen)? Das macht zwar die Pipeline etwas einfacher aber dafür muss man eben diesen Micro-Code bauen. Hm, ist wohl Geschmackssache.

Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?
Ja so umgefähr.
Ich hab da lieber gleich noch ein Bit mehr spendiert und habe dafür 4 unabhängige Flag-Sets, das dürfte gerade bei komplexen if-Abfragen oder auch verschachtelten if-Abfragen und Schleifen usw. einiges bringen.

Allerdings wollte ich doch eigentlich eine Programmiersprache und keine CPU bauen.
Ist doch hoffentlich kein Problem, oder? ;)


Dein OpCode-Format kannst Du ja, wenn es etwas näher an fertig ist, mal detaillierter vorstellen.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 20. January 2012, 21:19
Naja, ich hab mir mein Instruktionsformat noch mal überarbeitet:
Ein Befehl ist 32 bit lang, kann aber Abhängigkeiten von folgenden Befehlen besitzen. Es werden aber immer zwei 64 bit gleichzeitig eingelesen und abgearbeitet (das entspricht bis zu zwei Befehlen, die sich nicht in die Quere kommen sollten.)

Zu den Abhängligkeiten:
Das most significant Bit bestimmt die Bedeutung des dwords:
eine "0" entspricht Daten und eine "1" Befehle. Datenwörter, die nicht benutzt werden, entsprechen in der Ausführung nop.

Im der zweiten Piplinestufe (Dekodierstufe 1) wird das Packetpaar entsprechend vordecodiert. Hierbei entsteht je ein 28 bit Befehl und ein 64 bit Datenwort. Jeder der beiden Einzelbefehle besitzt hierzu einen Controlnibble (4 bit), der folgenden Effekt hat.
(Die Bitnummer beginnt bei 1)
0xxxxb - Datenwort (wird in nop umgewandelt)
1000b - Keine externen Daten benötigt, setzte das Datenwort auf 0.
1001b - 31 bit Immideate, die aus dem folgenden Datenbefehl geladen wird. Das 32igste bit wird auf 1 gesetzt die Zahl mit Nullen auf 64 bit erweitert.
1010b - 31 bit Immideate, die aus dem folgenden Datenbefehl geladen und mit Nullen auf 64 Bit erweitert wird.
1011b - 31 bit Immideate, die aus dem folgenden Datenbefehl geladen und mit Einsen auf 64 Bit erweitert wird.
11xyb - 2x31 bit Immideate, die aus 2 folgenden Datenbefehlen geladen wird und deren 32igstes bit auf y und deren 64igstes bit auf x gesetzt wird.
Wird versucht einen gültigen Befehl (1...b) als Datenbefehl zu übergeben, gibt es eine Invalid-Opcode Exeption

Da der Decoder 1. nicht über ausreichend Informationen verfügt, um die Daten vollständig zu ergänzen (er hat ja nur die zwei Befehle zu Verfügung, ergänzt er einfach die ihm bekannten Daten, sowie einige Steuersignale für die nächste Pipliestufe.
Da der Decoder 1 somit auch nicht wirklich 64 bit Datenwörter produziert, sind einige Bits im Ausgangsbus zusammengefasst und zwar:
(Die Bitnummerirung beginnt bei 0)

1. Befehl des Befehlspaars
Abschnitt - Möglicher Werte               Länge im Ausgangsbus
bit[30:0] - individuelle Daten              31
bit[31]                                           1
bit[62:32] - immer alle gleich              1
bit[64]                                           1
Gesamt                                         34

2. Befehl des Befehlspaars
Abschnitt - Möglicher Werte               Länge im Ausgangsbus
bit[30:0] - immer null oder egal           0
bit[31]                                           1
bit[62:32] - immer alle gleich              1
bit[64]                                           1
Gesamt                                          3

In der 3. Piplinstufe werden die 28 Bit Befehle dekodiert. Paralel werden die Datenwörter mithilfe des jetzt in der 2. Pipliestufen liegenden Befehlspaares verfolständigt.
Diese sogenannte Nachladeeinheit erhält von der Dekodierstufe 1 abgesehen von den beiden "64 bit" Datenwörtern folgende Steuersignale.
1. Befehl des Befehlspaars
pldctrl0 - Läd das erste 31 bit Datenwort aus der Dekodierstufe 1 in das 64 bit Datenwort an Prosition [62:32].

2. Befehl des Befehlspaars
pldctrl1 - Läd das erste 31 bit Datenwort aus der Dekodierstufe 1 in das 64 bit Datenwort an Prosition [30:0].
pldctrl2 - Läd das zweite 31 bit Datenwort aus der Dekodierstufe 1 in das 64 bit Datenwort an Prosition [62:32].

Nach dem die 3. Piplinestufe abgeschossen ist, wird das nun vollständige 64 bit Datenwort in das IR geladen.

Somit gibt es bei mir folgende Spezialregister, die aber ganz normal wie andere Register auch adressiert werden können, die aber für die beiden Befehle des Befehlspaares unterschiedlich sind:
IR
PC (ist für den zweiten Befehl beim lesen 4 höher.)

Sprünge werden für beide Befehle danach ausgeführt, sodass wenn der erste Befehl ein Sprung ist, der zweite trotzdem ausgeführt wird.

Die Sache mit dem Mikrocode muss ich mal schauen, wo ich die am besten unterkriege. Ich vermute aber in Piplinestufe 1. Ich hab mir das überlegt. Außerdem könnt man, wenn man den Baustein extern einrichtet, auf die Weise auch so eine Art BIOS unterbringen.
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 29. January 2012, 11:20
Hallo Sannaj,


ich hab noch mal etwas über Dein IR nachgedacht und mir sind da einige Probleme aufgefallen:
Wenn das Laden des IR und das Benutzen des IR mit unabhängigen Befehlen und in unterschiedlichen Pipeline-Stufen durchgeführt wird dann sehe ich ein Problem mit bedingten Sprüngen. Wenn nach so einem bedingtem Sprung ein IR-Lade-OpCode kommt und vom Decoder auch verarbeitet wird noch bevor für den bedingten Sprung feststeht ob dieser ausgeführt werden soll könnte es passieren dass das IR bereits mit einem neuen Wert geladen wird aber der Sprung trotzdem ausgeführt wird so das dann Befehle am Sprungziel im IR nicht den Wert vorfinden der als letztes vor dem bedingten Sprung geladen wurde sondern eventuell einen Wert der nach dem bedingtem Sprung geladen wurde. Ob das so passiert hängt von vielen Faktoren ab: vom Abstand (die Pipeline kann ja nur maximal eine gewisse Menge an Befehlen enthalten) aber auch davon ob der Speicher/Cache die nächsten Befehle schon an den Decoder geliefert hat. Damit würde die Situation entstehen das manche Befehle im OpCode-Stream die hinter dem bedingten Sprung kommen trotzdem ausgeführt werden und manche nicht und das auch noch ohne das der Compiler das immer eineindeutig ermitteln kann (eben weil das sicher auch von Umständen abhängt die erst zur Laufzeit bekannt sind und eventuell auch jedes mal anders ausfallen).
Ein weiteres Problem sind Exceptions und Interrupts. Du musst in der Lage sein das IR im Exception-Handler gezielt zu sichern und es am Ende des Exception-Handlers auch wieder herzustellen, ansonsten kannst Du nicht an beliebigen Stellen Exceptions/Interrupts zulassen.
In dieser Hinsicht gefällt mir das Konzept eines einfachen Load-Immediate-in-beliebiges-Register-Befehl am besten, entweder dieser Befehl wird in der Pipeline ausgeführt und ist damit für alle folgenden Befehle sichtbar oder er wird nicht ausgeführt und hat demzufolge auch keine sichtbaren Auswirkungen. Ich gebe zu das Dein IR durchaus seinen Reiz hat weil man damit mit jedem Befehl und für jeden Zweck ein Immediate benutzen kann aber wenn Du dafür eh ein Register opfern möchtest (also eine Register-Nummer) dann implementiere das als richtiges Register und mach einen Load-Immediate-Befehl der immer in dieses Register lädt und ganz normal von der Pipeline ausgeführt wird.

Nur 28 Bit für den eigentlichen Befehl würde ich persönlich als zu wenig betrachten, schau Dir mal an wie gedrängt es im klassischen ARM-Mode zugeht und dort wird die Bedingung (in den oberen 4 Bit) auch noch mitbenutzt um Befehle zu codieren. ARM hat auch nur 16 Register und das ist aus heutiger Sicht schon recht knapp bemessen.


Auf Micro-Code würde ich ganz verzichten, in einer RISC-CPU gibt es dafür IMHO keine Notwendigkeit. Ich glaube auch das Deine CPU dann die erste RISC-CPU wäre die Micro-Code benutzt und wie Du damit ein "BIOS" realisieren möchtest bzw. was Du damit meinst musst Du mal etwas genauer erläutern.


Grüße
Erik
Titel: Re: Eigene CPU
Beitrag von: erik.vikinger am 29. January 2012, 12:42
ich seh gerade das Dein Immedite ja nach dem Befehl kommt der dieses Immediate benutzen möchte, von daher ignoriert Bitte die erste Hälfte meines Beitrages
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 29. January 2012, 20:57
Zählst der Dritte Abschnitt auch noch zu den Sachen, die man auslassen soll?

Eine Zusätzlichen Befehl, welcher ausschließlich ein Register laden kann halte ich für nicht klug. Der Compiler kann das Register nur mit großen Aufwand für etwas anderes nutzten und die Immideate Instruktionen kosten Speicher (der Controlnibble lässt sich nicht so einfach einsparen.)
Titel: Re: Eigene CPU
Beitrag von: MasterLee am 16. February 2012, 09:06
Auf ein MOV würde ich auch grundsätzlich nicht verzichten, klar kann man das mit "ADD RD,RS,R0"
Da ich mit IR kein echtes 0-Register mehr habe, brauch ich natürlich auch einen echten MOV Befehl.
Kommt drauf an man könnte auch schreiben:
AND RD,RS,RS
oder
OR RD,RS,RS
Titel: Re: Eigene CPU
Beitrag von: Sannaj am 17. February 2012, 15:08
Logische Operationen mit sich selbst währen,

Zu der Sache mit dem BIOS,
würde ich die Mikrocodefunktionalität einbauen, müsste der Mikrocode ja nicht intern generiert werden. Der Mikrocodespeicher könnte auch extern liegen und über eine Schnittstelle eingelesen werden. Hiermit würde der Mikrocode eine Art Prozedursammlung (BIOS) bieten, aber ich glaube mittlerweile nicht mehr, das das so ne gute Idee war. Wenn, dann den Code direkt in den RAM einbinden.
Titel: Re: Eigene CPU
Beitrag von: MasterLee am 17. July 2012, 21:19
Ist noch jemand dran seine eigen CPU zu bauen?
Falls jemand zu viel Zeit hat hier ist eine kleine Anleitung wie man selbst einen einfachen IC macht:
http://www.youtube.com/watch?v=PdcKwOo7dmM
http://www.youtube.com/watch?v=eFzsyQOTXbM
Titel: Re: Eigene CPU
Beitrag von: nnosdev am 20. July 2012, 00:01
http://mycpu.eu/ (http://mycpu.eu/)
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.

Wahnsinn....