% Forth von der Pike auf Teil 2 % Content-encoding: UTF-8 \usepackage{soul} \begin{document} \title{Forth von der Pike auf — Teil 2} \author{Ron Minke} \maketitle Die hier mit freundlicher Genehmigung der HCC--Forth--gebruikersgroep wiederzugebende achtteilige Artikelserie erschien in den Jahren 2004 und 2005 in der Zeitschrift ``Vijgeblaadje'' unserer niederländischen Forth--Freunde.\\ Übersetzung: Fred Behringer. Hier ist der zweite Teil des Versuchs, ein Forth--System auf die Beine zu stellen, dessen Voraussetzung “überhaupt nix”, oder auf gut Deutsch “from scratch”, lautet. \begin{multicols}{2} \section*{16--Bit--Register auf dem AVR} In diesem Teil werden wir versuchen, die Register des AVR--Prozessors auf die Register der virtuellen Forth--Maschine abzubilden. Zuallererst müssen wir uns klar machen, dass wir drauf und dran sind, ein 16--Bit--Forth auf einem 8--Bit--Prozessor hochzuziehen. Wir werden also alle Register mit Register--\emph{Paaren} des AVR--Prozessors koppeln müssen (die zum Glück in genügender Zahl vorhanden sind). Zunächst noch schnell rekapitulieren, welche Register wir nötig haben: \begin{tabular}{lp{0.3\columnwidth}p{0.5\columnwidth}} PC& Ma\-schi\-nen\-pro\-gramm\-zähler &Der Motor des Mikro--Controllers.\\ \\ SP& Datenstack--Pointer & Zeigt auf das zur Zeit oberste Element des Datenstacks.\\ \\ RP& Returnstack--Pointer& Zeigt auf das zur Zeit oberste Element des Returnstacks.\\ \\ IP& Interpreter--Pointer& Zeigt auf die nächste Anweisung (Forth--Definition), die ausgeführt werden soll; überwacht die Reihenfolge der Ausführung.\\ \\ W& Wort--Pointer&Zeigt auf die Definition, die gerade ausgeführt wird; nötig, um den Parameterteil dieser Definition anzuspringen. \end{tabular} Die Wahl des Registers PC ist einfach: Der PC! Der Programmzähler eines Mikro--Controllers ist das einzige ``Register'', das das ``Runnen'' des eigentlichen Programms, des Low--Level--Programms, steuert. Der AVR--Prozessor hat 32 frei verfügbare 8--Bit--Register an Bord: R0 bis R31. Zum Glück haben die Prozessor--Entwickler gut nachgedacht: 8 Register können zu 4 Registerpaaren zusammengekoppelt werden. Das sind: \begin{tabular}{ll} \verb|R24| -- \verb|R25| & \verb|W|,\\ \verb|R26| -- \verb|R27| & \verb|X|,\\ \verb|R28| -- \verb|R29| & \verb|Y|,\\ \verb|R30| -- \verb|R31| & \verb|Z|. \end{tabular} Um die richtige Wahl treffen zu können, müssen wir uns die verschiedenen Eigenschaften der Registerpaare ansehen. \section*{Der Datenstack} Wir beginnen unsere Zuordnungssuche mit SP, dem Datenstack--Pointer. Wir wollen Daten auf den Datenstack legen. Hier begegnen uns schon vier Wahlmöglichkeiten: \begin{tabular}{lp{0.3\columnwidth}p{0.5\columnwidth}} &Pseudo--Code & Was der tut\\ \\ 1. &INC SP\newline MOV (SP),data & Datenstack wächst nach oben, erst Pointer anpassen, danach dann Daten ablegen.\\ \\ 2. & MOV (SP),data \newline INC SP & Datenstack wächst nach oben, erst Daten ablegen, danach dann Pointer anpassen.\\ \\ 3.& DEC SP\newline MOV (SP),data & Datenstack wächst nach unten, erst Pointer anpassen, danach dann Daten ablegen.\\ \\ 4. & MOV (SP),data\newline DEC SP & Datenstack wächst nach unten, erst Daten ablegen, danach dann Pointer anpassen. \end{tabular} Die Entscheidung darüber, ob der Datenstack nach oben oder nach unten wachsen soll, stellen wir noch etwas zurück. Allerdings wäre es besonders schön, wenn wir keine separaten Befehle INC oder DEC benötigen würden. Am liebsten hätten wir einen Befehl, der automatisch auch gleich noch inkrementiert bzw. dekrementiert. Beim AVR--Prozessor steht diese Möglichkeit für die Registerpaare X, Y und Z tatsächlich zur Verfügung. Mit dem Befehl ST (store) \begin{quote} \verb|ST X,R5| \end{quote} setzen wir den Inhalt des Registers R5 an die Stelle, wohin der Inhalt des X--Registerpaares zeigt. (Ausführliche Informationen finden sich auf der ATMEL--Webseite.) Der Befehl \begin{quote} \verb|ST X+,R5| (Auswahlmöglichkeit 2) \end{quote} macht dasselbe, aber danach wird in einem einzigen Zug auch noch der Inhalt des X--Registerpaares um 1 erhöht (post increment). Und gratis ist das obendrein: Die Post--Increment--Aktion kostet \emph{keinen} Extra--Maschinentakt. Der Befehl \begin{quote} \verb|ST -X,R5| (Auswahlmöglichkeit 3) \end{quote} arbeitet genauso, jedoch wird erst der Inhalt des X--Registerpaares um 1 erniedrigt, (pre decrement), bevor R5 auf dem dann angewiesenen Platz aufbewahrt wird. Nun müssen wir uns noch damit beschäftigen, was der SP--Pointer macht: SP zeigt auf das oberste (oder unterste, je nach Richtung) Element des Datenstacks. Wollen wir auf dem Stack neue Daten ablegen, dann muss \emph{erst} Platz gemacht werden, bevor wir die Daten abspeichern können. Von den oben genannten vier Möglichkeiten bleibt also nur die mit Nummer 3 übrig. Damit haben wir uns für ein Nach--unten--Wachsen des Datenstacks entschieden. Unsere virtuelle Forth--Machine ist 16 Bit breit, so dass wir nun für den 8--Bit--AVR--Prozessor zum folgenden \mbox{Code} kommen: (Daten stehen in R4 und R5 bereit) \begin{quote} \verb|ST -X,R4|\\ \verb|ST -X,R5| \end{quote} Man beachte, dass wir damit bereits den Maschinencode für das Forth--Wort ``!'' (store) gemacht haben. Aber gemach! Wir wollen unsere Wahl der AVR--Register--Zuweisung bequem gestalten: Sowohl das X-- wie auch das Y-- und das Z--Register haben die oben genannte Autodekrement--Eigenschaft. Die endgültige Wahl für SP muss also noch etwas zurückstehen. Wir bekommen es jetzt noch mit etwas anderem zu tun. Es muss noch beschlossen werden, was zuerst aufzubewahren ist: Das obere Byte oder das untere Byte der 16--Bit--Zahlenwerte. Oder auch: Wie wollen wir die Daten auf dem Stack gelagert sehen? Dem Forth--System selbst ist diese Frage egal. Wir müssen uns also nach einem anderen Kriterium umsehen. Die Entscheidung darüber, welche Wahl wir treffen, bleibt noch einen Augenblick lang offen (na ja $\ldots$ so dringend ist das noch nicht). \section*{Der Returnstack} Erst noch ein weiteres Forth--Register: Der RP. Der Returnstack heißt so, weil die virtuelle Forth--Maschine ihn dazu verwendet, die Rückkehr--Adressen (return = zurück) aufzubewahren, diejenigen Adressen, wo besagte virtuelle Maschine weiterarbeiten soll, \emph{nachdem} ein High--Level--Wort vollständig ausgeführt worden ist. Wenn ein High--Level--Forth--Wort ein schon früher definiertes anderes Forth--Wort aufruft, wird die Adresse des \emph{nächsten} Wortes in die Wortliste auf dem Returnstack eingereiht. Diese Adresse wird wiederhergestellt, sobald das gerade eben aufgerufene Wort vollständig abgearbeitet ist. Das Programm kann dann vom Verzweigungspunkt aus weitergehen. Der erste Gedanke, der aufkommt, ist der, ob man den RP nicht an den Maschinen--Stack--Pointer SP (nun, beim AVR heißt der nun einmal so) koppeln sollte. Dieses AVR--SP--Register hat genau dieselbe Funktion wie die, die wir bei der virtuellen Forth--Maschine haben wollen: Es zeigt auf die aufbewahrten Adressen, wo der Programm--Counter weiterarbeiten soll, wenn ein Unterprogrammaufruf beendet ist. Auto--Inkrement, bzw. Auto--Dekrement sind auch eingebaut. Beim Aufrufen eines Maschinensprach--Unterprogramms wird gleichzeitig die unmittelbar folgende Adresse auf den Returnstack gelegt. Wenn das Unterprogramm fertig ist, wird diese Adresse wieder in den Programm--Counter PC zurückgeschrieben, so dass das Programm weiterlaufen kann, als ob da nichts geschehen wäre. Also genau das, was wir brauchen. Was passiert da nun genau? Angenommen, ein willkürlich herausgesuchtes Stück AVR--Maschinencode ruft ein Unterprogramm auf (CALL). Verfolgt man den sich ergebenden CALL--Code (siehe ATMEL--AVR--Befehlssatz auf deren Website), so bekommt man die Sequenz (in Pseudo--Code, in Bytes): \begin{verbatim} MOV (AVR-SP),unteres-Byte-momentaner-PC + 1 DEC AVR-SP MOV (AVR-SP),oberes-Byte-momentaner-PC + 1 DEC AVR-SP MOV PC,(momentaner-PC) \end{verbatim} Wir sehen hier, dass der Returnstack nach \emph{unten} wächst und dass \emph{erst} die Daten abgespeichert werden, bevor Platz gemacht wird. Der AVR--SP zeigt also offenbar auf den ersten freien Platz auf dem Stack. So haben sich die Entwickler bei ATMEL das jedenfalls vorgestellt. \section*{Festlegungen} Nun wissen wir zumindest einiges. Beim Codieren der virtuellen Forth--Maschine wollen wir es uns so einfach wie möglich machen. Wir halten uns an die oben genannte Arbeitsweise. {\em Wir treffen drei Entscheidungen:} \begin{enumerate} \item Die Forth--Stacks SP und RP wachsen \emph{nach unten.} \item Das untere Byte eines 16--Bit--Wortes wird zuerst auf den Stack gelegt, darunter dann das obere Byte. \item Der Forth--Pointer zeigt auf das \emph{obere} Byte eines 16--Bit--Wortes (und also --- in Abweichung von der Arbeitsweise des AVR--SPs --- nicht auf den leeren Platz unter dem Wort). Das entspricht der Wahlmöglichkeit 3 bei der obigen Besprechung der Datenstack--Zuweisung. \end{enumerate} Nun denn$\ldots$ jetzt kommt allmählich etwas Struktur in die virtuelle Forth--Maschine. \hfill --- Wird fortgesetzt --- \end{multicols} \end{document}