ssfp16
Small Simple FPGA 16-bit Microprocessor

2 Prozessor

Der Prozessor-Kern ist in der Datei "ssfp16_test.vhd" beschrieben. Das angehängte "_test" bedeutet dabei nur, dass einige Signale zusätzlich herausgeführt sind - auf Größe und Geschwindigkeit hat dies keinen Einfluss.

Der Befehlssatz ist in ssfp16_befehlssatz.pdf tabellarisch dargestellt.

Der ssfp16 ist ein Prozessor in Harvard-Architektur, d. h. Befehle und Daten benutzen zwei völlig unabhängige Speicherbereiche mit unabhängiger Adressierung. Die Busstruktur des ssfp16-Kerns ist in Bild 1 dargestellt. Der obere Teil stellt den Datenpfad dar, im unteren Teil ist der Befehlspfad zu sehen. Sämtliche interne und externe Steuersignale inklusive der Parity-Bits sind hierin nur angedeutet.

Bild 1: Busstruktur mit Daten- und Befehlspfad.

2.1 Überblick Datenpfad

Der Datenpfad besteht aus den allgemeinen Registern, dem Konstanten-Register (imm), den Einheiten zur Bearbeitung von arithmetischen und logischen Operationen, dem internen Datenspeicher, dem Adress-Addierer sowie einigen Multiplexern zur Auswahl der Operanden und der Ergebnisse. Die meisten der Komponenten sind als einzelne VHDL-Beschreibungen implementiert, welche struktural zum Datenpfad zusammengeschaltet werden. Die Beschreibungen der untersten Ebene sind zum Teil speziell auf XILINX FPGAs zugeschnitten, da der Synthetisierer (zumindest ISE Webpack 8.1) leider bei Verhaltensbeschreibungen nicht immer optimale Ergebnisse liefert.

2.1.1 Register

Es gibt 16 weitgehend gleichberechtigte 16-bit Register, r0 bis r15 benannt. Diese werden als Registerbank bezeichnet. Einige der Register haben jedoch eine spezielle Bedeutung:

Die Registerbank ist in Form von sogenanntem "Verteilten RAM" im FPGA realisiert, d. h. SLICEMs werden genutzt. Da in vielen Befehlen gleichzeitig zwei Register benötigt werden, ist die Registerbank als Dual-Port-RAM ausgeführt. Der Schreib-Zugriff erfolgt synchron bei gesetztem WE, der Lese-Zugriff auf beiden Ports asynchron. Geschrieben werden kann nur auf Port A.

Zusätzlich gibt es einige separate Spezialregister:

Alle Spezialregister sind 16 Bit breit.

2.1.2 Datenfluss

Bei allen arithmetischen und logischen Operationen einschließlich Schieben, Multiplizieren und Dividieren bildet ein Register der Registerbank den ersten Operanden. Dieses steht an Ausgang OA an. Wenn ein zweiter Operand benötigt wird, so ist dieser entweder ein zweites Register der Registerbank (Ausgang OB) oder ein im Befehl codierter unmittelbarer Wert. Da in einem 16-bit Befehl kein 16-bit Wert Platz hat (schließlich muss der Befehl selbst ja auch noch codiert werden), wird ein unmittelbarer Wert aufgeteilt. Die unteren 4 Bit stehen dabei immer direkt in dem Befehl, in dem sie benötigt werden. Wenn mehr als 4 Bit benötigt werden, müssen die oberen 12 Bit in einem vorgestellten Befehl codiert werden. Diese werden dann einen Takt zuvor im Imm-Register gespeichert. Sofort nach dem anschließenden Befehl wird dieses Register wieder zurückgesetzt. Der Befehl zum Setzen des Imm-Registers wird ausschließlich vom Assembler selbst generiert und kann nicht manuell eingefügt werden.

Nach der Gatter-Durchlaufzeit der einzelnen Einheiten steht das Ergebnis der gewünschten Operation am Ausgang O des Haupt-Multiplexer MUX1 an. Mit der nächsten steigenden Taktflanke wird es in das durch ADA adressierte Register übernommen, der erste Operand wird also mit dem Ergebnis überschrieben.

Ein Multiplexer ist notwendig, da die Spartan3-FPGAs intern nicht über Three-State-Logik verfügen. Des Weiteren kann mit einer Gatterebene nur ein 8-zu-1 Multiplexer aufgebaut werden, benötigt werden aber mind. 14 Eingänge (ohne bitswap). Um zeitkritische Pfade nicht unnötig zu verlängern, wurden zwei 8-zu-1 Multiplexer kaskadiert anstatt einen 16-zu-1 Multiplexer zu beschreiben.

Soll ein Wert in ein RAM oder in eine Peripherie-Einheit kopiert werden, so wird dieser grundsätzlich aus dem durch ADA adressierten Register genommen.

Die Adress-Berechnung für RAM und Peripherie ist ebenfalls Teil des Datenpfades. Hierfür wird immer das durch ADB adressierte Register zu dem unmittelbaren Wert addiert. Der so berechnete (Adress-)Wert kann in das durch ADA adressierte Register geschrieben werden (siehe auch LEA und MOV weiter unten).

2.1.3 Addierer/Subtrahierer

Die arithmetischen Operationen sind:

Diese Operationen werden vom Addierer/Subtrahierer durchgeführt. Der Spartan3 unterstützt zwar Addierer/Subtrahierer in der benötigten Form hardwaremäßig ideal, leider steht aber keine entsichende Vorlage zur Verfügung - weder zur Instanziierung noch zum Inferieren. Daher habe ich diese Einheit gemäß der XILINX-Unterlagen struktural beschrieben. Eine funktionale Beschreibung wäre im Hinblick auf die Plattform-Unabhängigkeit natürlich schöner, aber seltsamerweise hatte ich an der Stelle keinen Ehrgeiz...

Hinweis: Bei Subtraktionen (einschließlich cmp) ist die Bedeutung des Carry-Flags invertiert. D. h. wenn das Ergebnis korrekt ist, ist Carry gesetzt. Wenn also bei sbb das Carry-Flag NICHT gesetzt ist, wird zusätzlich 1 subtrahiert. Dieses Verhalten ist von der Spartan3-Hardware so vorgegeben, stört aber meines Erachtens auch nicht, solange man nicht Assembler-Programme mit jc-Befehlen portieren will. Dann ist wirklich äußerste Vorsicht angesagt!

2.1.4 Logik-Einheit

Die logischen Operationen sind:

Die logischen Operationen werden in einem Spartan3-Funktionsblock pro Bit abgearbeitet, welcher ja genau die vier benötigten Eingänge bietet.

2.1.5 Schiebe-Einheit

Es existieren fünf Schiebe-Befehle:

Alle Befehle verschieben den Ausgang OA der Registerbank um eine Stelle. Es wird immer sowohl nach rechts als auch nach links verschoben, die Auswahl der gewünschten Operation findet erst anschließend durch den Multiplexer MUX1 statt.

2.1.6 Multiplizierer und Dividierer

Grundsätzlich steht eine vorzeichenlose (mul) und eine vorzeichenbehaftete (imul) Multiplikation zur Verfügung. Standardmäßig ist nur eine vorzeichenlose Division implementiert, Befehle für vorzeichenbehaftete Division sind jedoch reserviert. Als Operatoren stehen dieselben Quellen wie für die übrigen arithmetischen und logischen Befehle zur Verfügung.

Sowohl Multiplizierer als auch Dividierer besitzen jeweils zwei eigene 16-bit Ausgangsregister, da die Ergebnisse 32 Bit breit sind (Multiplizierer: High-Word und Low-Word des Ergebnisses, Dividierer: Quotient und Rest). Diese Register werden nur durch die jeweiligen Befehle (mul/imul, div/idiv) verändert, die Ergebnisse von Multiplikationen und Divisionen stehen also bis zur nächsten Multiplikation oder Division zur Verfügung. Mittels des movs-Befehls können die Ergebnisse in den internen Registern in die Registerbank übernommen werden.

2.1.7 BITSWAP, BYTESWAP und SETF

Alle drei Befehle arbeiten mit Ausgang OA der Registerbank als Operator.

2.1.8 Adressierung, LEA und MOV

Gemäß Spezifikation in der Einleitung soll es möglich sein, Register mit einer Konstanten zu laden (unmittelbare Adressierung), Werte von einem Register in ein anderes Register zu kopieren, eine konstante Speicherstelle in ein Register zu kopieren oder umgekehrt (direkte Adressierung), eine durch ein Register adressierte Speicherstelle in ein Register zu kopieren oder umgekehrt (indirekte Adressierung) sowie eine durch ein Register plus eine Konstante adressierte Speicherstelle in ein Register zu kopieren und umgekehrt (indirekte Adressierung mit Basisadresse). Zunächst könnte man auf die naheliegende Idee kommen, die Additionseinheit zur Berechnung der Adresse zu benutzen (also keinen Adress-Addierer einzuführen). Doch dann wäre zumindest bei der in Bild 1 gezeigten Busstruktur das Zielregister gleichzeitig das Adressregister, was in vielen Fällen unpraktisch wäre. Außerdem wäre es unmöglich, direkt eine Konstante zu laden, da diese ja immer zu einem Register addiert würde. Man müsste also vorher das Register immer erst löschen (z. B. durch ein and rx, 0), was einen zusätzlichen Befehl im (beschränkten) ROM und einen zusätzlichen Takt bedeuten würde. Da es auch möglich sein soll, ein Register in ein anderes zu kopieren, müsste man zusätzlich eine direkte Verbindung vom Ausgang OB der Registerbank zum Multiplexer erstellen. Das Laden einer Konstanten ist aber eine sehr häufig benötigte Operation, daher ist ein bisschen mehr Logik angebracht - der separate Adress-Addierer, welcher grundsätzlich Ausgang OB der Registerbank zu einer Konstanten addiert. Er ist 16 Bit breit. Führt man das Ergebnis in ein (beliebiges) Register der Registerbank, so bekommt man den lea ry, rx+const-Befehl. Man kann jetzt aber auch Register r0 (also 0) zu einer Konstanten addieren und in einem beliebigen anderen Register speichern - der mov rx, const-Befehl, oder aber ein beliebiges Register zu 0 addieren und in einem andern Register speichern - der mov ry, rx-Befehl.

Da nicht die Arithmetische Einheit für die Addition benutzt wird, bleiben Flags hiervon unberührt! (Man könnte natürlich unter Verwendung der ohnehin benötigten Hardware zumindest Vorzeichen- und Null-Flag setzen, das ist aber nicht üblich.)

2.1.9 Speicherbereiche

Im FPGA stehen RAM-Blöcke mit jeweils 4kB zuzüglich Parity-Bits zur Verfügung (BRAM). Diese werden sowohl für den Befehlsspeicher als auch für den Datenspeicher genutzt. Die Wortbreite der BRAMs ist beliebig wählbar aus 1, 2, 4, 8/9, 16/18 und 32/36 Bit. Benutzt man anstelle der 2 BRAMs also 4 BRAMs mit jeweils 4 Bit, so kann die Speichertiefe ohne weitere Änderung verdoppelt werden bis hin zu 32 kB (16 BRAMs je 1 Bit breit). Will man allerdings eine Parity-Prüfung machen, fallen ein bzw. zwei weitere BRAMs an. Alternativ könnte man natürlich auch immer 9-Bit breite BRAMs benutzen und einen externen Multiplexer gemäß des/der höchstwertigen Adressbits nachschalten und so die Parity-Bits der BRAMs auch bei größeren Speichern weiternutzen. Damit ergeben sich je nach Anwendungszweck und geforderter Zuverlässigkeit jedoch viele unterschiedliche Architekturen und so sah ich davon ab, die Speichertiefe bei RAM und ROM generisch zu beschrieben. Ohnehin müsste zumindest die Datei "ssfp16.bmm" manuell geändert werden.

Während für das ROM für sehr einfache Anwendungen ein BRAM (=1024 Befehle) ausreicht, müssen für den Datenspeicher mindestens zwei BRAMs benutzt werden, um High-Byte und Low-Byte unabhängig voneinander schreiben zu können.

Üblicherweise besitzen Prozessoren mit mehr als 8-bit Datenbreite Operationen zur Manipulation einzelner Bytes. Wie man jedoch am Befehlssatz erkennen kann, sind die 16 Bits der Befehle weitgehend ausgereizt. Will man also alle Befehle auch auf Bytes beziehen, so wäre man gezwungen, auch die Befehlsbreite zu vergrößern. Nun könnte man natürlich eines der beiden Parity-Bits des ROM noch als Befehlsbit nutzen, aber das wäre dann schon ein ziemlich unorthodoxes Vorgehen und auch wahrscheinlich schwer auf andere Architekturen übertragbar. Andererseits gibt es nur sehr wenige Punkte, an denen ein 16-Bit Wert anstelle eines 8-Bit Werts von Nachteil ist. Der schreibende Zugriff auf RAM dürfte eine dieser wenigen Anwendungen sein, müsste doch beim Schreiben eines Einzelbytes erst ein Wort gelesen, das jeweils zu überschreibende Byte mittels Verundung auf 0 gesetzt und anschließend durch Veroderung auf den neuen Wert gesetzt werden. Im schlimmsten Fall könnte das eigentlich nicht zu verändernde Byte sogar mittlerweile durch einen Interrupt verändert worden sein und würde dann wieder mit dem alten Wert überschrieben. Folglich müssten die Speicherbereiche sehr genau betrachtet oder grundsätzlich während solcher Operationen Interrupts vom Programm gesperrt werden. Aufgrund dieser Überlegungen entschied ich, prinzipiell keine Byte-Befehle vorzusehen außer zum Schreiben in RAM.

Datenspeicher wird auch bei vielen einfachen Anwendungen in relativ großen Mengen benötigt. Hier werden die vorhandenen BRAMs häufig nicht ausreichen, schließlich wird niemand einen teuren Riesen-FPGA kaufen, nur um ein paar zusätzliche kB RAM zu bekommen. Externer Datenspeicher wird daher häufig eingesetzt werden - sei es in Form eines echten RAM oder in Form eines (Flash-)ROM. Prinzipiell fallen mir drei Möglichkeiten ein, auf externen Speicher zuzugreifen:

  1. Der externe Speicher wird als peripheres Gerät angesehen, dem ein bestimmter (großer) Adress-Bereich zugeordnet ist. Dieses Verfahren wird bei Mikrocontrollern gelegentlich angewandt, bringt aber gelegentlich Nachteile.
  2. Der externe Speicher wird gleichberechtigt zum internen Speicher mit separaten Befehlen und separatem Adressbereich angesprochen. Dies ist das Standard-Verfahren für Mikroprozessoren ohne internen Speicher. Nachteil ist, dass zusätzliche Befehle nötig sind.
  3. Mittels eines DMA-Controllers, welcher parallel zum Prozessor in den internen Datenspeicher schreibt - welcher dann zumindest teilweise als Cache fungieren würde.

Ich habe mich für die zweite Variante entschieden, wobei die dritte Variante aufgrund der Dual-Port-Fähigkeit der BRAMs relativ leicht zusätzlich zu integrieren wäre.

Die Adress-Berechnung erfolgt wie oben beschrieben grundsätzlich mit 16 Bit. Somit können 64 kB Daten bzw. 65536 Befehle direkt angesprochen werden. Mittels eines Adress-Basis-Registers im I/O-Bereich können dennoch leicht auch beliebig große Datenspeicher angesprochen werden.

Insgesamt stehen damit folgende Befehle für das Lesen und Schreiben von RAM zur Verfügung:

Der ld-Befehl benötigt zwei Takte, da die BRAMs einen Register-Ausgang besitzen, welcher mit der nächsten Taktflanke erst gesetzt wird. Es wäre zwar möglich, durch Versatz der Taktflanken den BRAM-Ausgang im selben Prozessortakt zu setzen, jedoch müsste hierfür die Taktfrequenz deutlich reduziert werden - was insgesamt kontraproduktiv ist.

Hinweis: Bei Wort-Befehlen wird das LSB der Adresse ignoriert. Lesen und Schreiben von Worten erfolgt also immer mit Wort-Ausrichtung.

Hinweis: Bei stb und stxb und LSB der Adresse = 0 werden die Bits 15 bis 8 des Registers geschrieben, bei LSB = 1 die Bits 7 bis 0!

2.2 Befehlspfad

2.2.1 Befehls-ROM

Die Größe des im FPGA in Form von BRAMs verfügbaren ROM liegt in der für Mikrocontroller typischen Größenordnung von 4-32 kB und sollte für die meisten Anwendungen ausreichen. Andernfalls müsste der Befehlspfad um externes ROM ergänzt werden, was jedoch unter Beibehaltung der restlichen Architektur zu einer deutlichen Geschwindigkeits-Einbuße führen würde. In diesem Fall müsste also zumindest ein Befehls-Pipelining eingeführt werden, um einen ganzen Takt für das Auslesen des ROM zur Verfügung zu haben. Das ROM ist nur vom Befehlszeiger ip adressierbar und nur wortweise. Eine ROM-Adresse entspricht also 16 Bit (bzw. 18 Bit mit Parity) und einem Befehl. Von der Adresse-Einheit adressierbar sind somit 65536 Befehle oder 128 kB Befehls-ROM.

Ist Weiter gesetzt, so wird mit der nächsten Taktflanke der Befehl an der anliegenden Adresse ausgegeben und im selben Takt abgearbeitet (bzw. bei ld und div mit der Abarbeitung begonnen).

2.2.2 Befehlszeiger ip und Sprünge

Dieses Register ist so breit wie für die Adressierung des ROM benötigt - bei 4 kB = 2048 Befehlen also 11 Bit. Außer bei Start und Neustart enthält es immer die Adresse des gerade am Ausgang des ROM stehenden Befehls, nicht etwa die des nächsten Befehls.

Sofern der am Ausgang des ROM anstehende Befehl kein Sprungbefehl ist und kein Interrupt ausgeführt werden soll, wird der Wert des ip vom Adress-Addierer um 1 erhöht und an den Eingang des ip und den Adress-Eingang des ROM angelegt. Ist es ein bedingter Sprungbefehl (jxx abgekürzt, wobei xx für ein Bedingung steht), wird SpringeRel gesetzt und damit statt 1 ein im Befehl codierter vorzeichenbehafteter 8-bit Wert addiert. Dieser muss selbstverständlich vor der Addition vorzeichenbehaftet auf die Breite des ip erweitert werden. Somit sind bedingte Sprünge bis 128 Befehle zurück und 127 Befehle nach vorn möglich.

Unbedingte Sprünge (jmp) und Funktionsaufrufe (call) sind für den Befehlspfad identisch. In jedem Fall wird vom Adress-Addierer im Befehlspfad durch Addition eines Registers und einer Konstanten die neue Adresse berechnet und aufgrund des gesetzen Signals SetzeAdr gleichzeitig an die Eingänge von ip und ROM gelegt. Der Unterschied besteht lediglich darin, dass bei call, also wenn Bit 7 im Befehl gesetzt ist, noch ein WE für die Registerbank gesetzt wird und somit der Wert des ip zuzüglich 1 in das im Befehl codierte Register geschrieben wird - die Rücksprungadresse. Vom Assembler wird dieses Register bei einem call-Befehl immer auf 15 gesetzt, bei einem int-Befehl immer auf 14, für den Prozessor sind alle Register von 8 bis 15 möglich.

2.2.3 Interrupts

Für die meisten Anwendungen unentbehrlich sind Interrupts. Externe Interrupt-Anforderungen werden nur akzeptiert, wenn das "Interrupt Enable"-Flag (IE-Flag) gesetzt ist. Nach Start oder Neustart ist dieses zunächst nicht gesetzt, es muss erst explizit im Programm gesetzt werden. Bei Aufruf eines externen Interrupts wird es automatisch auf 0 gesetzt, beim Rücksprung mittels iret wird es wieder auf 1 gesetzt. Die Rücksprung-Adresse wird in Register r14 gespeichert (vom Prozessor vorgegeben), eine automatische Sicherung von Registern auf dem Stack erfolgt nicht, da es keinen dedizierten Stack gibt. Durch das IE-Flag ist jedoch sichergestellt, dass nach einem Interrupt-Aufruf erst alle Register gesichert werden können, ohne die Möglichkeit zu verwehren, rekursive Interrupts zu ermöglichen. Vor einen Interrupt-Rücksprung müssen Interrupts ggf. explizit gesperrt werden, bevor die letzten Register wie Rücksprungadresse und Flags wieder hergestellt werden - beim Rücksprung selbst werden Interrupts dann automatisch wieder freigegeben.

Im Befehlspfad wird ein Interrupt wie folgt abgearbeitet: Zunächst wird Weiter von der Steuerung zurückgenommen und SpringeIntr gesetzt. Damit wird ein Interrupt-Takt eingefügt - nichts anderes als ein von der Hardware angeordneter call nach 0 mit Speicherung der Rücksprungadresse in r14. Durch die Rücknahme von Weiter bleibt der Ausgang des Adress-Addierers auf dem nächsten auszuführenden Befehl stehen - der Rücksprungadresse. Das Einfügen des call-Befehls geschieht, indem ein Multiplexer in der Steuerung anstatt des Ausgangs des ROM (welcher in diesem Fall den bereits ausgeführten Befehl enthält) einen call-Befehl zur weiteren Dekodierung gibt. Das Zielregister r14 wird ebenfalls von der Steuerung generiert.

Da an Adresse 2 das "richtige" Programm beginnt (die erste Adresse nach Start und Neustart, siehe unten), muss an Adresse 0 oder 1 ein jmp zum Beginn der Interrupt-Routine stehen. In den meisten Fällen wird an Adresse 0 ein imm-Befehl für die oberen 12 Bit der Sprungadresse stehen.

Ein Interrupt-Takt darf auch bei gesetztem IE-Flag nicht jederzeit eingefügt werden:

Hinweis: Die Register MULTL, MULTH, QUOT und REST können nicht explizit geschrieben werden. Sie können daher in Interrupts nur dann genutzt werden, wenn sie im Hauptprogramm nicht genutzt werden oder wenn im Hauptprogramm jeweils für die Zeit, in der das Ergebnis benötigt wird, das IE-Flag auf 0 gesetzt wird. Da in Interrupts eigentlich grundsätzlich keine komplexen Rechnungen durchgeführt werden sollten, rechtfertigt diese Einschränkung meines Erachtens keine zusätzlichen, schreibbaren Register und entsprechende Befehle.

2.2.4 Start und Neustart (Restart)

Beim ersten Start (Initialisierung des FPGA) wird ip auf 1 gesetzt und der ROM-Ausgang (=Befehl) auf 0 gesetzt. Gleiches gilt für einen Neustart (Restart). Der ROM-Ausgang 0 entspricht einem sub r0, r0, also einem Befehl, der effektiv nichts tut, da r0 ohnehin 0 ist. Am Adress-Eingang von ROM und ip liegt dann 2, die Adresse des ersten Befehls. Kann nicht sicher ausgeschlossen werden, dass die Interrupt-Behandlung an einer Befehls-Adresse kleiner 16 beginnt, so muss der zweite Befehl im Assembler-Programm ein "unnötiger" Befehl sein.

2.3 Steuerung

Im Modul "Steuerung.vhd" werden alle zur Steuerung des Programm- und Datenflusses benötigten Signale generiert. Auch die Auswertung der Bedingungen für bedingte Sprünge geschieht in diesem Modul (vgl. Tabelle 1). Die wesentlichen Signale für die Steuerung des Programmablaufs wurden bereits im Abschnitt "Befehlspfad" genannt. Alle anderen dürften selbsterklärend sein.

Tabelle 1: Bedingte Sprungbefehle

Code Mnemonic Flags Bemerkung
0 jbe not C or Z Unsigned
1 jae, jc C Unsigned
2 jb, jnc not C Unsigned
3 ja not (not C or Z) Unsigned
4 jle (S xor O) or Z Signed
5 jge not (S xor O) Signed
6 jl S xor O Signed
7 jg not ((S xor O) or Z) Signed
8 js S
9 jns not S
10 jo O
11 jno not O
14 jz, je Z
15 jnz, jne not Z

2.4 Paritätsprüfung

Die Berechnung und Auswertung der Paritäts-Bits (Parity-Bits) des Datenspeichers erfolgt im Modul "datenpfad.vhd", die Auswertung für den Befehlsspeicher im Modul "befehlspfad.vhd". Es wurde ungerade Parität gewählt, da so auch leicht geprüft werden kann, ob eine Speicherstelle bereits initialisiert wurde. Wird lesend auf eine nicht initialisierte Speicherstelle zugegriffen, so enthält diese im Paritäts-Bit eine 0 und löst somit einen Paritätsfehler aus. Paritätsfehler werden als Signal nach außen gegeben und können dort beispielsweise eine Neu-Initialisierung, einen Interrupt oder eine Prozessorumschaltung auslösen.

Wird keine Paritätsprüfung benötigt, kann diese in den zuvor genannten Quelldateien entfernt werden und somit ca. 12 LUTs gespart werden. Der Enable-Eingang der Datenspeicher BRAMs kann dann immer auf 1 gesetzt werden.

Hinweis: Aufgrund eines Fehlers im ISE Webpack 8.1 ist es mit dieser Software nicht möglich, die Paritäts-Bits bei Erst-Start und Neustart richtig zu initialisieren. Daher wird beim Start immer ein Paritätsfehler für Befehls- und Datenspeicher ausgelöst. Je nach Behandlung der Paritätsfehler muss dies ggf. berücksichtigt werden.

Zurück: Einleitung, Übersicht Index Weiter: Assembler

Stand 16.09.2006

Copyright 2006 by Thomas Brunnengräber

Diese Dokumentation unterliegt den Bestimmungen der GNU Free Document License