%\documentclass[ngerman]{article} %\usepackage[T1]{fontenc} %\usepackage[latin1]{inputenc} %\setcounter{secnumdepth}{0} %\setcounter{tocdepth}{0} %\usepackage{alltt} % Forth von der Pike auf % Teil 3 %\begin{document} %\title{Forth von der Pike auf --- Teil 3} %\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. %Hier ist der dritte Teil des Versuchs, ein Forth--System auf die Beine zu %stellen, dessen Voraussetzung überhaupt nix, oder auf gut Deutsch \emph{from %scratch}\/, lautet. %\begin{multicols}{2} Nach den drei Entscheidungen, die wir im zweiten Teil getroffen haben, wollen wir uns jetzt dafür interessieren, wie wir Daten auf den Returnstack legen können. Für Returnstack--Operationen gibt es im AVR--Prozessor eigens zwei Maschinencode--Befehle: \begin{asm} \instr{PUSH}{legt Daten auf den Returnstack}\\ \instr{POP}{holt Daten vom Returnstack} \end{asm} Diese Befehle arbeiten so, als wenn in einem Unterprogrammaufruf CALL (in Pseudocode) stünde: \begin{small} \begin{asm} \lab{PUSH:}{}\\ \instr{MOV (AVR\_SP), Rn}{lege Daten von Register Rn ab}\\ \instr{DEC AVR\_SP}{zeige auf die neue leere Stelle}\\ \\ \lab{POP:}{}\\ \instr{INC AVR\_SP}{erhöhe Pointer, um an die Daten zu kommen}\\ \instr{MOV Rn, (AVR\_SP)}{kopiere Daten, lass alten Wert stehen} \end{asm} \end{small} Wie wir sehen, zeigt der Pointer auf einen freien Platz. Wir hatten uns aber vorgenommen (Entscheidung 3), genau das in unserer virtuellen Forth--Maschine {\bf nicht} zu tun. Doch noch ist nicht alles verloren: Mit den oben stehenden PUSH-- und POP--Befehlen können wir alle Returnstack--Operationen verwirklichen. Innerhalb der virtuellen Forth--Maschine ist es weniger von Belang, \emph{wo} der Pointer genau hinzeigt, solange wir nur an die Daten kommen. Wollen wir {\rm tatsächlich} wissen, wo der Pointer hinzeigt, dann müssen wir den Offset zwischen dem angezeigten freien Platz und dem Platz der eigentlichen Daten in Rechnung ziehen. Zum Glück beträgt dieser Offset nur 1 Byte. Unser Vorgehen, für den Forth--RP den AVR--SP zu wählen, stellt sich also als gangbar heraus. {\bf Entscheidung 4:} Das Forth--RP wird dem AVR--SP--Register zugewiesen. \section{Der Interpreter--Pointer} Das nächste Register der virtuellen Forth--Maschine, für das wir eine AVR--Lösung suchen wollen, ist der Interpreter--Pointer. Der IP zeigt auf den nächsten Befehl (Forth--Definition, Wort) der in unserem Forth--Programm ausgeführt werden soll. Der IP steuert die Reihenfolge der Ausführung auf dieselbe Weise, wie der Maschinenprogramm--Counter PC die Reihenfolge in einem Assembler--Programm bestimmt. Das wichtigste Codestück, das den IP verwendet und steuert, ist das Stück Code für NEXT (lesen Sie sich Teil 1 dieser Serie noch einmal durch). Sodann wird IP unter anderem in den Codeteilen verwendet, die Sprunganweisungen ausführen (BRANCH, LOOP u. dgl.). Der Vollständigkeit halber wiederholen wir das Stück Pseudo--Assembler--Code für NEXT: \begin{asm} \lab{NEXT:}{IP zeigt auf das als nächstes auszuführende Wort} \\ \instr{MOV W,(IP)}{kopiere den Inhalt von IP, die CFA des als nächstes auszuführenden Wortes, ins W-Reg.}\\ \instr{INC IP}{Lass IP auf das Wort \emph{nach} dem momentanen Wort zeigen, um dann dort ohne Umwege fortzufahren.}\\ \instr{MOV PC,(W)}{Führe den Code--Interpreter, dessen Adresse nun im W--Register sitzt, aus. Dieser Wert kommt aus dem Codefeld des momentan auszuführenden Wortes (indirekter Sprung).} \end{asm} Welches der AVR--Registerpaare W, X, Y und Z können wir hier nun am vorteilhaftesten einsetzen? Die Wahl wird eigentlich durch die letzte Zeile im Pseudoassemblercode bestimmt: \begin{asm} \instr{MOV PC,(W)}{} \end{asm} Hier wird indirekt auf eine Adresse gesprungen, die im Virtuellen--Forth--Register {\bf W} steht. Das einzige AVR--Registerpaar, das indirekte Sprünge zulässt, ist das Z--Registerpaar R30--R31 (Genaueres findet man im Befehlssatz auf der Atmel--Website). Wir benötigen das Z--Registerpaar als Zwischenschritt, um indirekt springen zu können. Leider verfügt der AVR nicht über Maschinenbefehle, die ein Registerpaar in einem einzigen Zug laden können. Also müssen wir das in zwei Schritten tun. Hierzu definieren wir {\bf ZL} (= R30) als den unteren Teil (low) des Z--Registerpaares und {\bf ZH} (= R31) als den oberen Teil (high). Wir brauchen auch ein W--Registerpaar zur Zwischenlagerung. Dafür wollen wir vorläufig das W--Registerpaar des AVRs wählen. Ob diese Entscheidung richtig war, werden wir später sehen (dass sich im AVR--Chip ein Registerpaar befindet, das auch W heißt, ist Zufall). Das W--Registerpaar spalten wir in zwei Teile auf, {\bf WL} (= R24) und {\bf WH} (= R25). Wenn wir den gesamten Pseudocode für NEXT hinschreiben, bekommen wir: \begin{footnotesize} \setlength{\tabcolsep}{5pt} \begin{tabular}{lrll} Pseudocode & & Assemblercode & NEXT--Routine\\ \hline \\ MOV W,(IP) & (1) & \inst{Ld WH,Rn} & indirekt hereinholen\\ INC IP & (2) & \inst{Inc Rn} & \\ & (3) & \inst{Ld WL,Rn} & indirekt hereinholen\\ & (4) & \inst{Inc Rn} &\\ & (5) & \inst{Mov Raaa,WH} & Pointer setzen\\ & (6) & \inst{Mov Rbbb,WL} &\\ MOV PC,(W) & (7) & \inst{Ld ZH,Raaabbb+} & indirekt, auto incr\\ & (8) & \inst{Ld ZL,Raaabbb} &\\ & (9) & \inst{IJmp} & indirekter Sprung\\ \end{tabular} \end{footnotesize} Uff, das ist ein schönes Stück Code! Um es in den Griff zu bekommen, nochmal alles der Reihe nach. Die Assembler--Befehle in den Zeilen 1--4 holen indirekt die Stelle herein, wo die \emph{nächste} Befehlsdefinition zu finden ist. Gleichzeitig wird der Pointer so erhöht, dass er auf die darauffolgende Definition zeigt. Die Zeilen 5 und 6 kopieren den hereingeholten Wert in ein Registerpaar Raaabbb. Das kann das X--, das Y-- oder das Z--Registerpaar sein. Auf welches die Wahl fällt, werden wir gleich sehen. Da unser Forth eine indirekt gefädelte Version (das klassische Modell) ist, müssen wir abermals einen indirekten Wert hereinholen, wenn wir erfahren wollen, wo der eigentliche Maschinencode für diese Definition steht. Die Zeilen 7 und 8 holen diesen indirekten Wert herein und legen ihn in das Z--Register. Wissen Sie es noch (Entscheidung 2): Erst das obere Byte hereinholen, dann das untere. Man beachte, dass dabei von der Auto--Inkrement--Funktion Gebrauch gemacht wird, so dass wir das Registerpaar Raaabbb nicht selbst zu erhöhen brauchen. Und schließlich wird über den Befehl IJmp in Zeile 9 der indirekte Sprung vollzogen. Der zum gerade auszuführenden Forth--Wort gehörende tatsächliche Maschinencode macht sich nun an seine Arbeit. Wir müssen noch festlegen, welches der in Frage kommenden Registerpaare X, Y und Z wir für das eben verwendete Registerpaar Raaabbb einsetzen wollen. Dabei müssen wir auch daran denken, dass die Register IP und SP der virtuellen Forth--Maschine noch endgültig festgelegt werden müssen. Große Auswahl haben wir eigentlich nicht, das Z--Registerpaar haben wir bereits für indirekte Sprünge verwendet. Bleiben für die Zuweisung an IP und SP das X-- und das Y--Registerpaar übrig (welches zu welchem, werden wir sogleich sehen). Zur Darstellung von Raaabbb bleibt also nur noch das Z--Registerpaar übrig. ??? Aber das Z--Registerpaar hatten wir ja gerade verwendet...??? Wir befreien uns aus dieser Situation, indem wir {\em beides} tun! {\bf Entscheidung 5:} Wir verwenden das Z--Registerpaar für Raaabbb \emph{und} auch für indirekte Sprünge. Durch wohlüberlegten Umgang mit dieser Kombination entsteht der folgende AVR--Maschinencode (wir benötigen dabei allerdings zwei Zwischenregister, für welche wir R0 und R1 nehmen). \begin{footnotesize} \setlength{\tabcolsep}{5pt} \begin{tabular}{lrlp{2.5cm}} Pseudocode && Assemblercode & Next--Routine\\ \\ MOV W,(IP) & (1) & \inst{Ld WH,Rn} & indirekt hereinholen\\ INC IP & (2) & \inst{Inc Rn} &\\ & (3) & \inst{Ld WL,Rn} & indirekt hereinholen\\ & (4) & \inst{Inc Rn} &\\ & (5) & \inst{Mov ZH,WH} & Pointer setzen\\ & (6) & \inst{Mov ZL,WL} &\\ MOV PC,(W) & (7) & \inst{Ld R0,Z+} & ind, oberes Byte, auto incr\\ & (8) & \inst{Ld R1,Z} & ind, unteres Byte\\ & (9) & \inst{Mov ZL,R1} & kopiere (Z) zurück nach Z\\ & (10) & \inst{Mov ZH,R0} & \\ & (11) & \inst{IJmp} & indirekter Sprung \end{tabular} \end{footnotesize} Wie sich herausstellt, gibt es im Befehlssatz der {\bf MEGA}--AVR--Prozessorserie einen Befehl, der ein Register{\bf paar} in einem einzigen Zug kopieren kann, so dass die Zeilen 5 und 6, bzw. 9 und 10 zu einem einzigen Mov zusammengefasst werden können. Für die kleinen AVR--Prozessoren trifft das jedoch nicht zu. Wir können aber auch von den Zwischenregistern noch etwas abknapsen. Bedenkt man, dass ZL auch ein gewöhnliches Register ist (und zwar R30) und dass man es auch als solches verwenden kann, können wir eines der Zwischenregister einsparen. Der Code: \begin{footnotesize} \setlength{\tabcolsep}{5pt} \begin{tabular}{lrll} 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} ist erfreulicherweise einen Befehl kürzer! Und angesichts der Tatsache, dass NEXT das am häufigsten gebrauchte Stückchen Forth--Code ist, nehmen wir das auch noch gern mit. Das Register R0 dient nach wie vor als Zwischenregister. %\hfill --- Wird fortgesetzt --- %\end{multicols} %\end{document}