%\documentclass[ngerman]{article} %\usepackage[T1]{fontenc} %\usepackage[latin1]{inputenc} %\setcounter{secnumdepth}{0} %\setcounter{tocdepth}{0} %\usepackage{alltt} % Forth von der Pike auf % Teil 4 %\begin{document} %\title{Forth von der Pike auf --- Teil 4} %\author{Ron Minke} %\maketitle %\newcommand{\inst}[1]{\texttt{#1}} %\newenvironment{asm}{\begin{center}\begin{tabular}{llp{0.6\columnwidth}}}{\end{tabular}\end{center}} %\newcommand{\lab}[2]{\multicolumn{2}{l}{\texttt{#1}}} %\newcommand{\instr}[2]{ &\inst{#1} & #2} %Die hier mit freundlicher Genehmigung der HCC--Forth--gebruikersgroep %wiederzugebende achtteilige Artikelserie erschien in den Jahren 2004 und 2005 %in der Zeitschrift \emph{Vijgeblaadje} unserer niederländischen Forth--%Freunde.\\ Übersetzung: Fred Behringer. %jetzt sind wird schon wieder beim vierten Teil des Versuchs, ein Forth--System %auf die Beine zu stellen, dessen Voraussetzung überhaupt nix, oder auf gut %Deutsch \emph{from %scratch}\/, lautet. %In der vorigen Folge hatten wir versucht, den Pointern IP und SP je ein AVR-%Registerpaar zuzuordnen. Beim Untersuchen der Möglichkeiten dazu hatten wir die %Verwendung des AVR-Z-Registers festgelegt. Wir verwenden es als Notizblock, als %einen Platz zum schnellen Zwischenspeichern, mit dem eigentlichen Ziel, einen %indirekten Sprung auszuführen. % \begin{multicols}{2} Wir hatten versucht, den Pointern IP und SP je ein AVR-Registerpaar zuzuordnen. Beim Untersuchen der Möglichkeiten dazu hatten wir die Verwendung des AVR-Z-Registers festgelegt. Wir verwenden es als Notizblock, als einen Platz zum schnellen Zwischenspeichern, mit dem eigentlichen Ziel, einen indirekten Sprung auszuführen. \section{Zuordnung der Forth-Register SP und IP} Dann wird es nun also Zeit, uns zu überlegen, welche Register wir den Pointern IP und SP zuordnen können. Wir haben noch die AVR--Registerpaare W, X und Y übrig. Für den Zugriff auf die Worte (à 16 Bits) im gesamten Forth--System wäre eine Auto--Inkrement--Funktion bequem. Das AVR--System ist 8 Bits breit, so dass wir die Daten so oder so in zwei Schritten hereinholen müssen. Das Register W hat keine Auto--Inkrement--Funktion, fällt also weg. Sodann würde es uns sehr zupass kommen, wenn wir bei Zugriffen auf den Datenstack nicht nur das oberste Element (eigentlich ja das unterste, der Stack steht Kopf) erreichen könnten, sondern auch die Daten von den Elementen weiter oben auf dem Datenstack. Der AVR--Befehlssatz hat dafür vorgesorgt: Das Y-- und das Z--Registerpaar können (in beschränktem Umfang) auch Daten mit einem Extra--Offset hereinholen. Und das ohne Rechenleistung. Der Offset sitzt ganz {\em normal} im Opcode. Das Z--Registerpaar haben wir bereits vergeben. Bleibt uns also das Y--Registerpaar. Es folgt ein Beispiel, um das noch etwas deutlicher zu machen: \begin{center} \begin{tabular}{rl} Stackposition& Wert\\ \hline 5 & Wort 3 unteres Byte\\ 4 & Wort 3 oberes Byte\\ 3 & Wort 2 unteres Byte\\ 2 & Wort 2 oberes Byte\\ 1 & Wort 1 unteres Byte\\ SP $\rightarrow$ 0 & Wort 1 oberes Byte \end{tabular} \end{center} Der Datenstack--Pointer SP zeigt auf einen Platz im RAM--Speicher. Wie vereinbart, steht dort das obere Byte eines Wortes. Wir greifen etwas vor und setzen Y auf den Wert von SP. Dieser Wert wird für die momentanen Erklärungen als Basiswert festgehalten. Mit dem Maschinenbefehl \begin{center} \inst{Ld R4,Y} \end{center} holen wir uns das obere Byte von Wort 1 und legen es ins Register R4. Und jetzt, ohne Extraberechnung: Mit dem Maschinenbefehl \begin{center} \inst{Ldd R5,Y+3} \end{center} laden wir auf einen Schlag das untere Byte von Wort 2 ins Register R5. Der hier verwendete Offset von 3 wird im Opcode automatisch verarbeitet. Es liegt nun also sehr nahe, dem Forth--Datenstack--Pointer SP das Registerpaar Y zuzuordnen... Das ist am Ende jener Platz, mit welchem das gesamte Forth--System arbeitet: Das System ist stack--orientiert. {\bf Entscheidung 6:} Der Forth--Datenstack--Pointer SP wird dem AVR--Registerpaar Y zugeordnet. Nun haben wir nur noch den Interpreter--Pointer IP übrig. Und es bleiben nicht mehr viel AVR--Registerpaare zu verteilen... Wir hatten bereits gesehen, dass eine Auto--Inkrement--Funktion für einen Pointer außerordentlich bequem ist. Für das Registerpaar, das wir für IP verwenden wollen, wäre diese Funktion auch sehr willkommen. AVR--Registerpaare mit Auto--Inkrement--Funktion sind X, Y und Z. Davon haben wir das Y-- und das Z--Paar bereits vergeben. Es bleibt uns also keine Wahl mehr! {\bf Entscheidung 7:} Der Forth--Interpreter--Pointer IP wird dem AVR--Registerpaar X zugeordnet. Wir können nun den (beinahe) endgültigen Code für NEXT zusammenstellen. Die Zeilennummerierung wurde unmittelbar vom Code oben übernommen. Jene Zeilennummern, die hier nicht mehr vorkommen, wurden dadurch eingespart, dass wir unsere Entscheidungen anpassten und pfiffige Code--Lösungen verwendeten. \begin{footnotesize} \setlength{\tabcolsep}{5pt} \begin{tabular}{lrlp{2.5cm}} Pseudocode & &Assemblercode & NEXT--Routine\\ \hline MOV W,(IP) &(1)& \inst{Ld WH,X+} &indirekt, auto--increment\\ INC IP &(2)& \inst{Ld WL,X+} & indirekt, auto--increment\\ &(5)& \inst{Movw ZL,WL} & kopiere Pointer\\ MOV PC,(W) &(7)& \inst{Ld R0,Z+} &ind, oberes Byte, auto incr\\ &(8)& \inst{Ld ZL,Z} &ind, unteres Byte\\ &(10)& \inst{Mov ZH,R0} &kopiere nur das obere Byte\\ &(11)& \inst{IJmp} &indirekter Sprung \end{tabular} \end{footnotesize} Das Einzige, was wir uns noch überlegen müssen, ist die Frage, ob unsere Wahl des AVR--Registerpaares W für das Forth--Register W die richtige Wahl war. Um das beurteilen zu können, müssen wir im Forth--Prozess noch einen Schritt weitergehen, nämlich zur Behandlung von High--Level--Worten. \section{Die Behandlung von High--Level--Worten} In einem High--Level--Wort, das aus einer :--Definition besteht, enthält das Parameterfeld der Definition eine Liste mit Adressen (mit CFAs, wie eingangs bereits erklärt) von anderen Worten, die ausgeführt werden sollen. Die Verarbeitungs--Routine dieser High--Level--Forth--Worte muss diese Adressenliste in der richtigen Reihenfolge abarbeiten. Das geschieht im Adressen--Interpreter DOCOL. DOCOL verwendet den Interpreter--Pointer IP auf dieselbe Weise, wie der AVR--Programmzähler PC die Maschinenbefehle verarbeitet. Anders gesagt, der IP läuft durch die Adressenliste im Parameterfeld so, wie der Programmzähler durch die Folge von Maschinenbefehlen läuft. Wenn die CFA auf eine andere High--Level--Definition zeigt, muss IP verwendet werden, um durch die neue Liste von Adressen zu laufen. Den alten Wert von IP bewahren wir auf dem Returnstack (daher der Name) auf, um später wieder zurückkehren zu können. Damit wird IP wieder zur Verarbeitung der neuen Liste frei. Auf diese Weise bildet der Returnstack eine Erweiterung von IP, so dass es möglich wird, ein weiteres High--Level--Wort aus einem anderen heraus aufzurufen. Die maximale Anzahl von Worten, die sich eines aus dem anderen heraus aufrufen können (die Nesteltiefe) hängt ausschließlich vom Platz auf dem Speicher ab, den der virtuelle Forth--Computer dem Returnstack zur Verfügung stellt. Am Ende einer :--Definition muss die Kontrolle wieder an das aufrufende Wort zurückgegeben werden. Das besorgt der EXIT--Code. Die Rückkehradresse hatten wir auf dem Returnstack (dessen Bezeichnung jetzt klar wird) aufbewahrt. Wir können den gesamten Sachverlauf in Pseudocode fassen: \begin{asm} \lab{DOCOL:}{Das W--Register zeigt auf die CFA des momentan gerade ausgeführten Wortes}\\ \instr{DEC RP}{Schaffe Platz auf dem Returnstack.}\\ \instr{MOV (RP),IP}{Setz die Adresse des als nächstes auszuführenden Wortes auf den Returnstack; wir benötigen IP zum Durchlaufen der neuen CFA--Liste.}\\ \instr{INC W}{Lass W auf die PFA des laufenden Wortes zeigen, auf die erste Adresse in der Liste mit CFAs.}\\ \instr{MOV IP,W}{Kopiere diese Adresse des ersten Wortes aus der neuen CFA--Liste nach IP, bereit zur Verwendung durch NEXT.}\\ \instr{NEXT}{Führe den Code für NEXT aus (siehe oben), um dieses neue Wort auszuführen.} \end{asm} Man beachte, dass wir zwei dieser Befehle durch einen einzigen ersetzen können: \begin{center} \parbox{2.2cm}{\inst{DEC RP}\\\inst{MOV (RP),IP}}$\rightarrow$ \inst{PUSH IP} \end{center} Der PUSH--Befehl erledigt auf einen Schlag beide Dinge zugleich. Wenn wir auf diese Weise die gesamte Liste von High--Level--Worten durchgearbeitet haben, müssen wir wieder dorthin zurückkehren, wo wir hergekommen sind. Dafür sorgt der EXIT--Code oder das ; am Ende unserer Forth--Definition. \begin{asm} \lab{EXIT:}{Die Rückkehradresse liegt auf dem Returnstack.}\\ \instr{MOV IP,(RP)}{Stelle vom Returnstack aus die Adresse des als nächstes auszuführenden Wortes wieder her.}\\ \instr{INC RP}{Gib den frei gewordenen Platz auf dem Returnstack wieder zurück.}\\ \instr{NEXT}{Führe NEXT aus, um dort fortzufahren, wo wir nach Ausführung dieses Wortes verblieben waren.} \end{asm} Auch hier können wir zwei dieser Befehle durch einen einzigen ersetzen: \begin{center} \parbox{2.2cm}{\inst{MOV IP,(RP)}\\\inst{INC RP}}$\rightarrow$ \inst{POP IP} \end{center} Der POP--Befehl erledigt auf einen Schlag beide Dinge zugleich. Nun übersetzen wir den oben stehenden Pseudocode in AVR--Maschinenbefehle. Ein funktionierendes, im klassischen Sinne aufgebautes AVR--Forth rückt näher! % \hfill --- Wird fortgesetzt --- %\end{multicols} %\end{document}