Ich habe über Arduino und die AVR-Architektur gelesen und bin an dem Punkt hängengeblieben, wie das Blockieren oder Blubbern von Pipelines durch die Einführung der Harvard-Architektur im AVR gelöst wird. Ich meine, Harvard bietet nur unterschiedliche Speicherorte für Datenspeicher und Programmspeicher an ermöglicht das Laden von Programmen ohne Bediener. Aber wie hilft es, das obige Problem zu lösen?
Die Harvard-Architektur, die übrigens lange vor der Erfindung von AVRs verwendet wurde, hat tatsächlich getrennte Adressräume für den Programmspeicher und für den Datenspeicher. Was dies der Partei bringt, ist die Möglichkeit, die Schaltung so zu gestalten, dass ein separater Bus und eine separate Steuerschaltung verwendet werden können, um den Informationsfluss vom Programmspeicher und den Informationsfluss zum Datenspeicher zu handhaben. Die Verwendung der getrennten Busse bedeutet, dass es möglich ist, das Abrufen und Ausführen des Programms ohne Unterbrechung durch eine gelegentliche Datenübertragung zum Datenspeicher fortzusetzen. Beispielsweise kann in der einfachsten Version der Architektur die Programmabrufeinheit damit beschäftigt sein, die nächste Anweisung in der Programmsequenz parallel zu einer Datenübertragungsoperation abzurufen, die Teil der vorherigen Programmanweisung gewesen sein kann.
Auf dieser einfachsten Ebene hat die Harvard-Architektur insofern eine Einschränkung, als es generell nicht möglich ist, Programmcode in den Datenspeicher zu legen und von dort aus auszuführen.
Es gibt viele Variationen und Komplexitäten, die dieser einfachsten Form der Architektur, die ich beschrieben habe, hinzugefügt werden können. Eine übliche Ergänzung ist das Hinzufügen von Befehls-Caching zum Programminformationsbus, das der Befehlsausführungseinheit einen schnelleren Zugriff auf den nächsten Programmschritt ermöglicht, ohne dass sie zu einem langsameren Speicher gehen muss, um den Programmschritt jedes Mal, wenn er erforderlich ist, abzurufen.
Einige Anmerkungen zusätzlich zu Michaels Antwort:
1) Die Harvard-Architektur erfordert nicht, dass es zwei getrennte Bereiche für Daten und Code gibt, sondern dass sie (meistens) über zwei verschiedene Busse abgerufen werden .
2) Das Problem, das durch die Harvard-Architektur gelöst wird, ist Buskonkurrenz: Bei einem System, bei dem der Codespeicher die Anweisungen gerade schnell genug bereitstellen kann, um die CPU mit voller Geschwindigkeit laufen zu lassen, verlangsamt die zusätzliche Belastung durch das Abrufen/Speichern von Daten die CPU Nieder. Dieses Problem wird durch eine Hardvard-Architektur gelöst: ein Speicher, der (etwas) zu langsam für die Geschwindigkeit der CPU ist.
Beachten Sie, dass Caching eine weitere Möglichkeit ist, dieses Problem zu lösen. Oft werden Harvarding und Caching in interessanten Kombinationen eingesetzt.
Harvard benutzt zwei Busse. Es gibt keinen grundsätzlichen Grund, bei zwei zu bleiben, in sehr speziellen Fällen werden mehr als zwei verwendet, hauptsächlich in DSPs (Digital Signal Processors).
Memory Banking (im Sinne der Verteilung von Speicherzugriffen auf verschiedene Chipsätze) kann als eine Art Harvarding innerhalb des Speichersystems selbst angesehen werden, das nicht auf der Daten/Code-Unterscheidung basiert, sondern auf bestimmten Bits der Adresse.
Eine reine Harvard-Architektur ermöglicht im Allgemeinen, dass ein Computer mit einem bestimmten Komplexitätsgrad schneller läuft als eine Von-Neuman-Architektur, vorausgesetzt, dass keine Ressourcen zwischen dem Code und den Datenspeichern geteilt werden müssen. Wenn Pinbelegungsbeschränkungen oder andere Faktoren die Verwendung eines Busses für den Zugriff auf beide Speicherräume erzwingen, werden solche Vorteile leicht zunichte gemacht.
Eine "reine" Harvard-Architektur ist auf die Ausführung von Code beschränkt, der von einem anderen Mechanismus als dem Prozessor, der den Code ausführt, in den Speicher gestellt wird. Dies schränkt die Nützlichkeit solcher Architekturen für Geräte ein, deren Zweck nicht von der Fabrik (oder jemandem mit spezialisierter Programmierausrüstung) festgelegt wird. Zwei Ansätze können verwendet werden, um dieses Problem zu lindern:
Einige Systeme haben separate Code- und Speicherbereiche, stellen aber spezielle Hardware bereit, die aufgefordert werden kann, kurzzeitig den Codebus zu übernehmen, einige Operationen durchzuführen und die Steuerung an die CPU zurückzugeben, sobald eine solche Operation abgeschlossen ist. Einige dieser Systeme erfordern ein ziemlich ausgefeiltes Protokoll, um solche Operationen auszuführen, einige haben spezielle Anweisungen, um eine solche Aufgabe auszuführen, und einige wenige suchen sogar nach bestimmten "Datenspeicher" -Adressen und lösen die Übernahme/Freigabe aus, wenn versucht wird, auf sie zuzugreifen . Ein Schlüsselaspekt solcher Systeme ist, dass es explizit definierte Speicherbereiche für „Code“ und „Daten“ gibt; selbst wenn es der CPU möglich ist, "Code"-Raum zu lesen und zu schreiben, wird er immer noch als semantisch verschieden vom Datenraum erkannt.'
Ein alternativer Ansatz, der in einigen High-End-Systemen verwendet wird, besteht darin, einen Controller mit zwei Speicherbussen zu haben, einen für Code und einen für Daten, die beide mit einer Speicherzuteilungseinheit verbunden sind. Diese Einheit wiederum ist mit verschiedenen Speichersubsystemen verbunden, wobei für jedes ein separater Speicherbus verwendet wird. Ein Codezugriff auf ein Speichersubsystem kann gleichzeitig mit einem Datenzugriff auf ein anderes verarbeitet werden; Nur wenn Code und Daten versuchen, gleichzeitig auf dasselbe Subsystem zuzugreifen, müssen beide warten.
Auf Systemen, die diesen Ansatz verwenden, können nicht leistungskritische Teile eines Programms einfach die Grenzen zwischen Speichersubsystemen ignorieren. Wenn sich der Code und die Daten zufällig in demselben Speichersubsystem befinden, laufen die Dinge nicht so schnell wie in separaten Subsystemen, aber für viele Teile eines typischen Programms spielt das keine Rolle. In einem typischen System gibt es einen kleinen Teil des Codes, bei dem die Leistung wirklich eine Rolle spielt, und er arbeitet nur mit einem kleinen Teil der vom System gespeicherten Daten. Wenn man ein System mit 16 KB RAM hätte, das in zwei 8-KB-Partitionen unterteilt ist, könnte man Linker-Anweisungen verwenden, um sicherzustellen, dass sich der leistungskritische Code in der Nähe des Anfangs des gesamten Speicherplatzes befindet und die leistungskritischen Daten in der Nähe des Ende. Wenn die Gesamtcodegröße auf z. B. 9K anwächst, Code innerhalb der letzten 1 KB würde langsamer ausgeführt als Code, der an anderer Stelle platziert wird, aber dieser Code wäre nicht leistungskritisch. Wenn der Code beispielsweise nur 6 KB groß wäre, die Daten aber auf 9 KB anwachsen, wäre der Zugriff auf die niedrigsten 1 KB an Daten langsam, aber wenn sich die leistungskritischen Daten an anderer Stelle befinden, wäre das kein Problem.
Beachten Sie, dass die Leistung zwar optimal wäre, wenn der Code unter 8 KB und die Daten unter 8 KB wären, das oben erwähnte Speichersystemdesign jedoch keine strikte Aufteilung zwischen Code- und Datenraum auferlegen würde. Wenn ein Programm nur 1 KB Daten benötigt, kann der Code auf 15 KB anwachsen. Wenn nur 2 KB Code benötigt werden, könnten die Daten auf 14 KB anwachsen. Viel vielseitiger als einen 8K-Bereich nur für Code und einen 8K-Bereich nur für Daten.
Ein Aspekt, der nicht diskutiert wurde, ist, dass für kleine Mikrocontroller, typischerweise mit nur einem 16-Bit-Adressbus, eine Harvard-Architektur den Adressraum effektiv verdoppelt (oder verdreifacht). Sie können 64 KB Code, 64 KB RAM und 64 KB speicherabgebildete E/A haben (wenn das System speicherabgebildete E/A anstelle von Portnummern verwendet, wobei letztere bereits die E/A-Adressierung vom Code & trennen RAM-Speicherplätze).
Andernfalls müssen Sie den Code, den RAM und optional die E/A-Adresse alle in denselben 64-KB-Adressraum packen.
PeterJ
Ayush
PeterJ