Um die für das Hilfs-FPGA zu generierende Hardware maschinell verwertbar zu beschreiben, wurde die standardisierte Hardware-Beschreibungssprache VHDL (Very high speed integrated circuit Hardware Description Language) benutzt. Diese unterstützt hierarchische Entwürfe; wegen ihres Umfanges und der thematischen Abgeschlossenheit bot es sich dabei an, das Interface zum PC und die Ansteuerung des 20x2-Zeichen-Displays in eigenständige, untergeordnete Beschreibungen auszulagern und diese auf der höchsten Ebene zu referenzieren. Diese höchste Ebene findet sich in der Datei ctrl_pur.vhd.
Wie aus dem Blockschaltbild des Hilfs-FPGAs (3.3.1) hervorgeht, verbleiben
folgende Komponenten im Top-Level-Design:
Nachfolgend sind obige Punkte in der Reihenfolge ihres erstmaligen Erscheinens in der VHDL-Beschreibung erläutert.
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Display Anfang ---" und "--- Display Ende ---".
Folgende Operationen sind auf dem memory mapped arbeitenden Display
für den Mikroprozessor möglich:
Adressbits 19 … 4 |
Adressbits 3 … 0 |
Datenbit 0 |
Zugriffsart |
Wirkung |
**1* | 1 | Schreiben | user_mode_set '1', d.h. Display geht in User Mode, Anzeige von Adr. u. Datum stoppt. | |
9000 - | **1* | 0 | Schreiben | user_mode_reset '1', d.h. Display beendet User Mode, Adr. u. Datum werden wie gewohnt angezeigt, falls eines gültig anliegt. |
97FF
(Adressbereich |
**00 | * | Schreiben | user_disp_go '0'; Display beginnt, falls im User Mode, mit der Anzeige von user_char, ansonsten keine Wirkung. |
Display) | **00 | user_disp_busy | Lesen | Display berichtet, ob noch mit Zeichenanzeige beschäftigt (user_disp_busy = '1'). |
**01 | * | Schreiben | Speichern der unteren neun Datenbits in user_char (disp_rs und eigentliches Zeichen). |
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Tastatur Anfang ---" und "--- Tastatur
Ende ---".
Zur Funktionalität: Zusammenfassend gesagt wird mit jedem Takt genau eine Spalte der Tastenmatrix per Schieberegister tast_spalte_t (tast_spalte) aktiviert, die Rückantwort (auf tast_reihe) ausgewertet, das tast_reg entsprechend gesetzt und eventuell ausgegeben sowie nachfolgend gelöscht.
Die "Hex-Tastatur" mit 20 Tasten ist als Matrix von einfachen
Schaltern (Drucktaster) mit 5 Spalten und 4 Reihen realisiert. Die Eingänge
aller Schalter einer Spalte hängen an tast_spalte(x), die Ausgänge
führen in jeder Zeile auf tast_reihe(y). Keine zwei Schalter sind also
mit Ein- und Ausgang an den gleichen Signalen angeschlossen. Nun
wird durch tast_spalte immer genau eine Spalte mit '1' getrieben, alle
anderen mit '0'. Meldet eine Reihe durch '1', daß in ihr eine Taste gedrückt
wird, so muß es sich um diejenige Taste handeln, die in der gerade getriebenen
Spalte liegt.
Falls mehrere Tasten gleichzeitig gedrückt werden sollten, könnte es bei diesem Aufbau zu Kurzschlüssen zwischen zwei Spalten kommen. Deswegen werden die fünf Spaltensignale zwischen Hilfs-FPGA und Tastaturmatrix durch Schutzdioden in Durchlaßrichtung geführt, welche die Nullen nicht durchlassen; es trifft also immer '1' auf 'Z' (Leitung offen), was sicher wieder '1' ergibt.
Damit das Drücken einer Taste in der gerade aktivierten Spalte sich
von dem in einer anderen Spalte bzw. keinem Tastendruck unterscheidet,
damit also '1' und 'Z' an tast_reihe unterschiedlich aussehen, wurde tast_reihe
mit Pull Down-Widerständen versehen, die aus 'Z' eine (schwache)
'0' machen.
Das ringförmige Schieberegister tast_spalte_t enthält immer genau eine
Eins und rotiert mit jeder Taktrückflanke um eine Stelle (Prozeß TASTATUR_ANSTEUERUNG);
tast_spalte wird direkt aus tast_spalte_t abgeleitet.
Mit der Vorderflanke wird (im Prozeß TASTATUR_REGISTER), das tast_reg
modifiziert, welches mit seinen zwanzig Bits widerspiegelt, welche Tasten
seit dem letzten Löschen (durch Auslesen) gedrückt worden sind. In nachstehender
Reihenfolge werden Bedingungen überprüft und gegebenenfalls Veränderungen
durchgeführt:
Die ungleichen Hälften des tast_reg werden durch Setzen von tast_reg_clear(0/1)
als zu löschen markiert.
tast_reg(x) (tast_reihe(a) AND tast_spalte_t(b)) OR tast_reg(x);
Das ODER-Gatter sorgt durch Verknüpfung mit dem alten Wert dafür, daß
eine '1' stets erhalten bleibt (gelöscht wird ja durch den separaten Vorgang
des Auslesens). Die mit UND verknüpften Signale sind quasi die Koordinaten
einer Taste in der Matrix; eine bestimmte Taste wird genau dann gerade
gedrückt, wenn ihr Reihenausgang '1' liefert und ihre Spalte gerade getrieben
wird.
Folgende Operationen sind auf der memory mapped arbeitenden Tastatur
für den Mikroprozessor also möglich:
Adressbits 19 … 4 |
Adressbits 3 … 0 |
Datenbits 15 …0 |
Zugriffsart |
Wirkung |
9800 - 9FFF
(Adressbereich Tastatur) |
***0 | tast_reg(15 downto 0) | Lesen | Untere "Hälfte" von tast_reg wird auf Datenbus gelegt und mit der nächsten Vorderflanke, zu der m_oe wieder '1' ist (Lesen beendet), gelöscht. |
***1 | "ZZZZZZZZZZZZ" &
tast_reg(19 downto 16) |
Lesen | Analog für obere "Hälfte" von tast_reg. |
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Tastatur Ende ---" und "--- Synchronisation Anfang
---" und lautet dort:
clk1_democom clk AND clk_aktiv; -- Democom bekommt Takt wie Hilfs-FPGA,
-- maskiert mit
clk2_democom NOT (clk AND clk_aktiv); -- clk_aktiv aus
dem PC-Interface.
Das Signal clk_aktiv wird in pc_if.vhd aus dem Statusregister
und dem sstep_pulse_ff abgeleitet; der Mikroprozessor wird mit einem Taktsignal
versorgt, falls er
Um Funktions-Hazards auf dem Taktsignal für den Mikrorechner
auszuschließen, ändern sich status_reg und sstep_pulse_ff, und damit auch
clk_aktiv, mit der Rückflanke; letzteres hat sich also rechtzeitig
vor der bevorzugt benutzten Vorderflanke stabilisiert. Wie man sich
leicht überlegen kann, ist auch der Verlauf der Rückflanke für den Mikroprozessor
aller Wahrscheinlichkeit nach unkritisch; drei Fälle sind dabei zu unterscheiden:
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Tastatur Ende ---" und "--- Synchronisation Anfang
---" und lautet dort:
m_highz m_highz_t;
m_highz_t '0' when ((mem_read = '1') OR (mem_write = '1')) else '1';
Jedes Design eines Mikroprozessors muß, falls das Hilfs-FPGA den Speicher beeinflussen und kontrollieren können soll, folgende Signale freigeben können, d.h. nicht mehr treiben: Adreßbus (address), Datenbus (databus), Output Enable (m_oe), Write Enable (m_we).
Diese Funktionalität wurde deswegen in die Datei altera1500.tdf integriert, die Grundlage jedes Prozessorentwurfs sein muß, u.a. um gleiche Pinbelegungen sicherzustellen. Wird das Steuersignal m_highz auf '0' gezogen, so werden die oben genannten Signale asynchron und ohne Einflußmöglichkeit des Prozessors mittels Tri-State-Treibern vom Rest des Mikroprozessorboards getrennt.
Das temporäre Signal m_highz_t wird verwendet, da m_highz in der VHDL-Beschreibung als Ausgang definiert ist, wodurch es nicht mehr möglich ist, seinen Wert abzufragen. Genau dieses passiert aber in der RAM-Zugriffsmaschine, die ja für einen Lese- oder Schreibzyklus m_oe und m_we steuern muß, sie aber natürlich erst treiben darf, wenn die Signale "frei" sind.
Die "Mem"-Signale originieren im PC-Interface und initiieren einen Speicherzugriff, indem die RAM-Steuerungsmaschine ihren Wartezustand verläßt (siehe hierzu Kap. 3.5 Abhängigkeiten der Automaten). Das PC-Interface läßt die Aussendung beider Signale nur im Zustand halted zu.
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Tastatur Ende ---" und "--- Synchronisation Anfang
---" und lautet dort:
a_or_d_changed <= '1' when ((m_oe = '1') AND (m_we = '0')) OR -- Jemand schreibt in SRAM oder
((m_oe = '0') AND (m_we = '1')) OR -- jemand liest aus SRAM oder
(m_reg_ack = '0') -- Democom-Register ist da
else '0';
pc_if_data pc_if_data_out when (pc_if_driven = '1') else "ZZZZZZZZ";
-- Getrennte In-/Out-Busse wie
pc_if_data_in pc_if_data; -- in altera1500.tdf
Das Signal a_or_d_changed "erkennt" aus den typischen Wertekombinationen von m_oe und m_we einen lesenden oder schreibenden Zugriff auf den Speicher und aus dem affirmativen Signal m_reg_ack, ob der Mikroprozessor gerade den angeforderten Wert eines seiner internen Register auf Adreß- oder Datenbus legt. In allen drei Fällen hat sich der Wert mindestens eines Busses geändert, so daß als Folge areg_muxout und dreg_muxout deren Werte speichern (falls der PC diese abfragen sollte) und die Display-Ansteuerung dann mit dem Neuaufbau der Anzeige beginnt, falls sie sich nicht im User Mode befindet.
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Synchronisation Anfang ---" und "--- Synchronisation
Ende ---".
Die Signale reset_unsync, m_obf_unsync und ibf_unsync müssen vor ihrer Verwendung (als Signale ohne das Suffix "_unsync") mit dem Taktsignal für das Hilfs-FPGA synchronisiert werden. Dies geschieht in gleicher Weise mit der Taktrückflanke, indem bei deren Auftreten der momentane Wert der Signale in drei Flipflops festgehalten wird.
reset bedarf der Synchronisation, da bei einem asynchronen Zurücksetzen
folgendes Szenario möglich wäre:
Alle Zustandsübergangslogiken enthalten einen "when others"-Fall, so daß bei Vorliegen einer nicht erlaubten Zustandskodierung ebenfalls der Initialzustand eingenommen werden sollte. Jedoch sind zwei Ausnahmen denkbar: Erstens könnte Max+plus II während der Logiksynthese erkennen, daß dieser Fall, von reiner, nicht mit Laufzeiteffekten behafteter Logik ausgehend, ja gar nicht eintreten kann und die zu dessen Behandlung vorgesehene Logik einfach fortlassen. Zweitens blieb in der Testphase des Hilfs-FPGAs unklar, ob verbotene Zustände nur Folge oder (auch) Ursache der unerklärlichen "Abstürze" dieses Bausteines waren (siehe 2.4.4).
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- Chipselect Anfang ---" und "--- Chipselect Ende
---".
Diese Logik unterteilt den Speicher in überschneidungsfreie Intervalle, so daß bei einem Zugriff auf eine bestimmte Adresse höchstens ein Gerät aktiviert ist. Den Speicher teilen unter sich auf: Hauptspeicher, parallele I/O, serielle I/O, EPROM, Display, Tastatur und Mikrocode-RAM. Nur die ersten vier erhalten tatsächlich ein Chipselect-Signal, welches die entsprechenden Bausteine entweder für normalen Betrieb zur Verfügung stellt, oder so deaktiviert, als seien sie von allen Bussen abgeklemmt. Für die nächsten beiden wird ein internes Signal erzeugt, so daß das Hilfs-FPGA weiß, wann auf diese memory mapped arbeitenden Komponenten zugegriffen wurde (wobei es dann noch verschiedene Adressen zu unterscheiden gilt, siehe 2.3.1.1 für Display und 2.3.1.2 für Tastatur).
Auf das Mikrocode-RAM wird zugegriffen, indem der PC das, auf dem realen Adreßbus gar nicht vorhandene, 21. Adreßbit auf '1' setzt (welches in areg_rollin(20) gepuffert wird).
Zur Funktionalität: Der Code für alle Chipselect-Signale lautet:
if ((mem_write = '0' AND mem_read = '0') OR areg_rollin(20) = '0') then
-- Eine Komponente kann Chipselect bekommen, falls entweder das Hilfs-FPGA _nicht_ den Adressbus treibt, oder
-- es das zwar tut, aber im Bereich der I/O-Geraete (21. Adressbit ist '0').
case
(Hier werden die Chipselect-Signale für alle mit 0 beginnenden Adressen vergeben wie in der Tabelle unten)
end case;
else
chipsel "111111"; -- keine Komponente aktiv, da Adresse im Mikrocode-RAM
end if;
Hierzu muß man beachten, daß das Altera 81500-FPGA nicht nur Prozessor-Designs beherbergen darf, sondern auch das sogenannte Ladedesign. Nur mit diesem ist es möglich, das Mikrocode-RAM zu beschreiben oder auszulesen, denn dieses RAM hängt nicht an den allgemeinen Adreß- und Datenbussen, sondern direkt am 81500-FPGA. Das Ladedesign tut nichts anderes, als Adreß- und Datenbus sowie Output Enable und Write Enable durchzureichen.
Mit der Entscheidung, ob man das 21. Adreßbit setzt oder nicht gibt
es somit vier Fälle:
Im Zweifelsfall empfiehlt es sich daher, den 81500 mit einem Prozessor-Design
neu zu konfigurieren, wenn in die untere Speicherhälfte geschrieben werden
soll und nicht ausgeschlossen werden kann, daß er noch das Ladedesign enthält.
Die Aufteilung des Speichers schlußendlich sieht also so aus:
Adreßbereich |
Komponente |
Chipselect geführt an Bausteine |
000000 - 01FFFF | SRAM | IC8 RAM1, IC9 RAM0 |
080000 - 087FFF | Parallele I/O | IC12 PIO_8255 |
088000 - 08FFFF | Serielle I/O | IC13 UART-2681 |
090000 - 097FFF | Display | (Signal nur intern verwendet) |
098000 - 09FFFF | Tastatur | (Signal nur intern verwendet) |
0A0000 - 0BFFFF | - | - |
0C0000 - 0DFFFF | EPROM | IC10 EPROM1, IC11 EPROM0 |
0E0000 - 0FFFFF | - | - |
100000 - 1FFFFF | Mikrocode-RAM | IC3 RAM4, IC4 RAM3, IC5 RAM2, IC6 RAM1, IC7 RAM0 |
Die Beschreibung findet sich in ctrl_pur.vhd im Prozeß BUSSE.
Zur Funktionalität: Normalerweise treibt das Hilfs-FPGA die beiden Busse auf der Mikrorechnerplatine nicht, überläßt sie dem Wechselspiel von Mikrorechner und anderen Komponenten. In der VHDL-Beschreibung wird den Bussen dann ein Vektor "Z...Z" passender Länge zugewiesen.
Wenn das Hilfs-FPGA selbst "aktiv" wird, i.e. Speicher auslesen oder beschreiben möchte, wird der Adreßbus address mit dem Wert von areg_rollin beschickt, der Datenbus databus wird analog aber nur beim Schreiben mit dreg_rollin getrieben, beim Lesen wird natürlich von "draußen" ein Datenwert erwartet. In zwei anderen Fällen reagiert das Hilfs-FPGA nur "passiv" auf (unter anderem) eine bestimmte Adresse, so daß der Adreßbus in diesen Fällen selbstverständlich nicht getrieben wird. Diese beiden Fälle sind:
Erstens das Abfragen von user_disp_busy (siehe 2.3.1.1), um zu erfahren, ob das 20x2-Zeichen-Display seinen Auftrag schon erfüllt hat, ein benutzerdefiniertes Zeichen anzuzeigen. Dabei wird das niedrigstwertige Bit des Datenbusses mit user_disp_busy beschickt. Zweitens das Abfragen des tast_reg (siehe 2.3.1.2), um zu ermitteln, ob und welche Tasten gedrückt wurden. Da das tast_reg breiter als 16 Bit ist, werden entweder seine unteren 16 Bit, oder die oberen vier, wieder ergänzt durch Teile von dreg_rollin, auf den Datenbus aufgebracht.
Die Beschreibung findet sich in ctrl_pur.vhd nach dem Prozeß
BUSSE bis zum Dateiende (Prozesse ADRESSREGISTER, DATENREGISTER und DATENREGISTER2).
Zur Funktionalität: Die vier Register areg_muxout, dreg_muxout, areg_rollin
und dreg_rollin dienen hauptsächlich der Pufferung der Busse und der Bitbreitenumsetzung
zwischen PC-Interface und Mikroprozessorplatine. Ihre Anordnung und Wirkung
läßt sich am kürzesten so darstellen:
Adreßbus (address) -/20 areg_muxout -/8 PC-Interface (pc_if_data)
PC-Interface (pc_if_data) -/2*4 areg_rollin
-/20 Adreßbus (address)
Datenbus (databus) -/16 dreg_muxout -/8 PC-Interface (pc_if_data)
PC-Interface (pc_if_data) -/2*4 dreg_rollin
-/16 Datenbus (databus)
Die "Mux-Out"-Register wirken in Richtung zum PC und sorgen dafür, daß gültige Adressen und Daten jederzeit gespeichert werden können, um sie dann byteweise zu übertragen. Adresse und Datum sind dann gültig, wenn "jemand" (i.e. der Mikrorechner oder das Hilfs-FPGA) den Speicher ausliest oder ihn beschreibt oder wenn der Mikrorechner mit m_reg_ack signalisiert, daß die Rückantwort beim Auslesen eines seiner internen Register bereitsteht; das Lesen und Schreiben im Speicher läßt sich durch die Werte von m_oe und m_we erkennen, nämlich die Kombinationen ((m_oe = '0') AND (m_we = '1')) respektive ((m_oe = '1') AND (m_we = '0')). Das Suffix "_muxout" ergibt sich daraus, daß die Registerinhalte, aus Sicht des Hilfs-FPGAs, nach "auswärts" geschickt werden und daß mit Hilfe eines Multiplexers acht der 16 bzw. 20 Bits zur Übertragung ausgewählt werden.
Die "Roll-In"-Register wirken in Gegenrichtung und akkumulieren
die Bytepakete des PC-Interface zu Adressen/Daten korrekter Länge. Dabei
sendet der PC zwar grundsätzlich acht Bits gleichzeitig, diese werden jedoch
in zwei Takten als Pakete zu je vier Bit in das Register hineingeschoben;
damit bleibt die Möglichkeit erhalten, alternativ oder additiv eine vierbittige
Datenquelle, wie z.B. eine Hexadezimal-Tastatur, anzuschließen. Die Inhalte
dieser Register sind zu verstehen als der "nächste zu schreibende
Wert" und die "nächste anzulegende Adresse", denn will der
PC eine Speicherzelle beschreiben, so sendet er drei Kommandos über das
PC-Interface:
Soll von einer Adresse gelesen werden, kann der zweite Schritt selbstverständlich fortfallen. Durch diese grundsätzliche Vorgehensweise ist es möglich, einen Speicherbereich zu initialisieren, indem man nur einmal std sendet, und nachfolgend vor jedem wr lediglich die Adresse verändert. Andererseits kann die zuletzt beschriebene Speicherzelle einen neuen Wert zugewiesen bekommen, indem man nur std (und natürlich wr) befiehlt.
Die Beschreibung findet sich in ctrl_pur.vhd zwischen den Marken
"--- RAM Zugriffsmaschine Anfang ---" und "--- RAM Zugriffsmaschine
Ende ---".
Dieser endliche Automat ist in 3.4.2 dargestellt, in 3.5.2 unter Berücksichtigung der Abhängigkeit von anderen Automaten.
Die Übernahme des Folgezustandes erfolgt mit der Rückflanke, da eine Abhängigkeit zum ersten Automaten aus dem PC-Interface besteht, welcher mit der Vorderflanke läuft. Die Beschreibung erfolgte wie üblich in zwei (parallel ablaufenden) Prozessen (ZUGRIFF und RAM_IF_SYNCH; siehe Kap. 2.3.4 "A guide to Carsten's VHDL").
Zur Funktionalität: Der Automat wartet nach Beendigung eines Schreib- oder Lesezyklus so lange, bis ihm durch write_mem '1' bzw. read_mem '1' der Auftrag für das nächste Schreiben/Lesen gegeben wird. Dieses wird durch Durchlaufen zweier Zustände erledigt, wonach der Automat wieder in den Wartezustand zurückkehrt. Der letzte Zustand eines Zyklus sendet das Signal mem_ready aus, welches wiederum der erste Automat im PC-Interface abfragt.
Ein dritter Prozeß RAM_IF_STEUERSIGNALE treibt, sobald write_mem oder read_mem auf '1' gehen, die Ausgänge m_oe (Output Enable) und m_we (Write Enable) in Abhängigkeit vom Zustand des RAM-Zugriffsautomaten. Das dabei entstehende Timing bei Lese- und Schreibvorgängen zeigen die Schaubilder 3.2.1 und 3.2.2 .