\documentclass[ngerman]{article} \usepackage[T1]{fontenc} \usepackage[latin1]{inputenc} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \usepackage{alltt} % Forth von der Pike auf % Teil 7 und 8 %\begin{document} %\title{Forth von der Pike auf — Teil 7 und 8} %\author{Ron Minke} %\maketitle \renewcommand{\inst}[1]{\texttt{#1}} \renewenvironment{asm}{\begin{center}\begin{tabular}{llp{0.5\columnwidth}}}{\end{tabular}\end{center}} \renewcommand{\lab}[2]{\multicolumn{2}{l}{\texttt{#1}}} \renewcommand{\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 kommen die Folgen sieben und acht der Wiedergabe des Versuches, ein AVR--Forth--System %mit der Voraussetzung from scratch zu erstellen. (Ed.: Um die VD--Leser nicht allzu lange warten zu %lassen, wurden die Teile 7 und 8, die vorläufig letzten beiden des holländischen Originals, in der %vorliegenden deutschen Übersetzung zusammengefasst.) \begin{figure*}[b] \begin{center} \includegraphics[width=\textwidth]{2007-Sonderheft-AVR/Teil7-8-Bild1} \caption{\label{minke7:abb1} Schaltbild des Versuchsaufbaus} \end{center} \end{figure*} \begin{multicols}{2} In der vorigen Folge haben wir gesehen, dass bei Verwendung eines AVR--Prozessors die klassische, indirekt gefädelte Methode für unser Forth--System die einzige in Frage kommende Möglichkeit ist. In der vorliegenden Folge überlegen wir uns, wie das eine oder andere zu implementieren ist. Wir gehen von einer Standard--Ausführung aus. Die besteht aus einem AVR--Prozessor, einem Adress--Latch und einem RAM--Speicher. Das übliche EPROM fehlt; das sitzt bereits als FLASH--Speicher im Prozessor selbst. Wir gehen davon aus, dass im Prozessorchip ein serieller Anschluss in Form eines UARTs zur Verfügung steht. Natürlich müssen wir auch einen AVR--Prozessortyp wählen, der überhaupt externes RAM ansteuern kann (das können sie längst nicht alle). Das im Prozessor eingebaute RAM verwenden wir auch (dadurch wird ein kleiner Teil des externen RAMs außer Kraft gesetzt). In unserem Beispiel wählen wir einen ATmega162--Prozessor in Verbindung mit einem RAM von 32 Kilobyte (Schaltbild siehe Abbildung \ref{minke7:abb1}). \section{Stackgröße} \begin{figure*} \begin{center} \includegraphics[width=\textwidth]{2007-Sonderheft-AVR/Teil7-8-Bild2} \caption{\label{minke7:abb2}Das Wort DUP, teils im RAM, teils im Flash} \end{center} \end{figure*} Zuerst müssen wir ein paar Annahmen machen. Wie groß sollen wir den Datenstack wählen, und wie groß den Returnstack? Wenn wir sie alle beide im internen RAM des Prozessors unterbringen können, dann haben wir auf jeden Fall die allerschnellste Konfiguration. Der Zugriff auf das interne Prozessor--RAM kostet nur 1 Maschinenzyklus, während ein Zugriff auf das externe RAM 2 Zyklen verlangt. Hier bietet sich der erste Zeitgewinn an. Für ein Stardard--Forth für Experimentierzwecke reichen uns fürs Erste ein Datenstack von 32 Worten und ein Returnstack von ebenfalls 32 Worten. Der ATmega162 hat ein internes RAM von 1024 Bytes, also genügend Platz, um tatsächlich beide Stacks intern aufzunehmen. {\bf Entscheidung 10:} Der Datenstack und der Returnstack sind je 32 Worte lang und befinden sich im internen RAM des Prozessors. \section{Über den Forth--Code} Dieses experimentelle Forth ist nach dem FIG--Modell (aus dem Jahre 1982!) aufgebaut. Das deshalb, weil das Modell an zahlreichen Literaturstellen beschrieben wurde. Ein erschöpfendes Buch ist \emph{The Forth Encyclopedia} von Mitch Derick und Linda Baker. Das FIG--Modell geht jedoch von einem Forth aus, das vollständig im RAM läuft. An vielen Stellen im System werden da Pointer angepasst und Code--Worte hinzugefügt. In Folge 6 unserer Serie haben wir gesehen, dass das in der AVR--Umgebung nun gerade nicht geht. Mit einigem Erfindungsgeist lässt sich hierfür aber eine Lösung finden. Wenn wir uns eine Methode ausdenken, bei der alle Low--Level--Codeworte ins Flash wandern und alle High--Level--Worte ins RAM, dann können wir uns dem Forth in normaler High--Level--Art nähern. Das Selbermachen von Code--Worten ist dann jedoch nicht möglich. Es geht also darum, einen wohldurchdachten Kernel zu entwickeln, der bereits alles enthält, was wir haben wollen! \section{Der Low--Level--Kernel} Woraus besteht der Kernel--Code des Forth--Systems nun eigentlich? Richtig: Aus CODE. Wir müssen uns gut klarmachen, dass ausschließlich Code (Maschinenbefehle) ausgeführt werden kann. Dass die Forth--Codeworte auch einen Namen haben, ist nebensächlich, aber angenehm. Und wenn wir jetzt alle Maschinenbefehlsteile auf einen Haufen ins Flash werfen, und alle Namen ins RAM bugsieren? Dann entsprechen wir auf jeden Fall dem AVR--Prozessor--Modell. Aber mit Code--Worten ohne Namen haben wir noch kein Forth--System. Auf die eine oder andere Art müssen wir doch die Namen verarbeiten können. Die Auflösung: Wir stellen eine vorgefertigte Liste auf, die nur die Namen der Code--Worte enthält, setzen die in den Flash--Speicher und kopieren beim Hochfahren des gesamten Systems sämtliche Codewort--Namen ins RAM. Die Forth--Worte können dann in gewohnter Weise dadurch angesprochen werden, dass man ihre Namen im RAM--Speicher aufsuchen lässt und anschließend den zugehörigen Maschinencode im Flash--Speicher zur Ausführung bringt. Der zugehörige Maschinencode wird über einen Pointer, die CFA, erreicht. Zur Verdeutlichung die Darstellung des Wortes \inst{DUP} (siehe Abbildung \ref{minke7:abb2}). Der Header und das Codefeld befinden sich im RAM--Speicher, wobei das Codefeld auf ein Stückchen Maschinencode auf Adresse 0530 im Flash--Speicher zeigt. Wir sehen hier auch, dass neben dem Namen des Forth--Wortes auch die Linkfeld--Adresse des vorhergehenden Wortes und natürlich die CFA im RAM--Speicher liegen müssen. Der Maschinencode des Parameterfeldes liegt im Flash--Speicher. Damit haben wir ein Standard--Forth erzeugt, mit der Besonderheit, dass die Bytes nicht allesamt hintereinander im RAM--Speicher liegen, sondern sich auf zwei Bereiche verteilen, die ihren Platz in zwei verschiedenen, voneinander getrennten Speicherteilen haben. Das geht problemlos, solange nur alle Zeiger auf den richtigen Platz verweisen. Es sollte klar sein, dass die Suchfunktion (ein Low--Level--Codewort im Flash), die die Forth--Worte in der Wortliste aufsucht, ihre Suchaktion vollständig im RAM--Speicher (dem Datenaufbewahrungsort) ausführt. Bevor das alles richtig ineinanderpasst, ist noch einiges an Knobelarbeit zu verrichten. \section{Der High--Level--Kernel} \begin{figure*}[t] \begin{center} \includegraphics[width=0.7\textwidth]{2007-Sonderheft-AVR/Teil7-8-Bild3} \caption{\label{minke7:abb3}Speicheraufteilung nach der Kopieraktion} \end{center} \end{figure*} Neben den Low--Level--Codeworten besteht der Forth--Kernel auch aus High--Level--Worten. Zunächst einmal müssen wir uns vergegenwärtigen, dass ein High--Level--Wort ein echtes Forth--Wort ist, das also aus Daten besteht. Für Maschinencode gibt es hier nichts zu tun. Die Bytes, die da stehen, sind ganz und gar reine Daten. Es braucht hier kein Maschinencode ausgeführt zu werden. Die Forth--Maschine ist ja eine virtuelle Maschine. Die eigentliche Arbeit wird von einem ganz kleinen Stück speziellem Maschinencode geleistet, von \inst{NEXT}. Wir können in den Kernel sehr wohl eine Anzahl von High--Level--Worten aufnehmen, die dann aber als vorgefertigte Daten vorliegen müssen, welche beim Hochfahren erst ins RAM zu kopieren sind. Wir müssen also in den vorgefertigten Worten alle Kopplungen (Links) und Verweise auf andere High--Level--Worte von Anfang an sorgfältig setzen. Das Ganze bekommt erst dann seinen vollen Wert, wenn es ins RAM kopiert ist. Auf dem ursprünglichen Platz im Flash lässt sich nichts damit anfangen. Hier liegt eine auserlesene Aufgabe für einen Forth--META--Compiler vor (ein META--Forth ist ein Forth, mit welchem man ein anderes Forth aufbauen kann). Eine Version, die mit einem eigenständigen AVR--Assembler (beispielsweise den von Atmel, dem Hersteller des Atmega162) erzeugt wurde, ist natürlich auch möglich, aber das kostet viel mehr Mühe. Allerdings weiß man bei Verwendung eines Assemblers bis auf den letzten Maschinenbefehl, wie das aufzubauende Forth--System aussieht. Abbildung \ref{minke7:abb3} gibt eine Vorstellung davon, wie die Dinge nach der Kopieraktion beim Hochfahren im Speicher eingeteilt sind. % (Übers.: Hier beginnt die Übersetzung des ursprünglich achten Teils des holländischen Originals.) In Abbildung \ref{minke7:abb3} haben wir gesehen, wie wir mit einer Kopieraktion das vorgefertigte Forth vom Flash ins RAM verfrachten können. Das liefert uns ein funktionierendes Forth--System. Mit solch einem System(chen) kann man bereits viele Dinge auf einem selbstentwickelten Hardware--Experimentieraufbau ausprobieren. Unangenehm am Quelltext einer solchen Forth--Version ist der Umstand, dass man schon beim Entwurf darauf achten muss, dass die vorgefertigten Worte im RAM am richtigen Platz zu liegen kommen. Eigentlich wäre hier ein gehörig großer RAM--Bereich eine schöne Sache: Es wäre dann Platz genug da, um den vorgefertigten Anteil an High--Level--Worten ins RAM zu kopieren. Später angefügte Definitionen fänden dann ihren Platz oberhalb des so kopierten Teils. Aber was ist, wenn ein hinreichend großer RAM--Bereich gar nicht vorhanden ist? Können wir unser Forth--System dann immer noch verwenden? Das widerspricht doch eigentlich unserem Ausgangspunkt, nämlich dem Entwurf eines Forth--Systems ganz aus dem Nichts. \section{Das Unmögliche wurde doch möglich} Es ist natürlich eine Extraherausforderung, ein Forth auf einem nackten Prozessor ohne externes RAM zum Laufen zu bringen. Im ersten Moment denkt man: Das klappt nie, das funktioniert nicht. Wenn Sie jedoch das hier Geschriebene lesen, werden Sie begreifen, dass es sehr wohl gelungen ist. Aber wie können wir denn den vorgefertigten Block an High--Level--Worten auf einem nur recht kleinen Bereich an internem RAM unterbringen? Die Antwort lautet: Das tun wir nicht. Wir lassen den vorgefertigten Block einfach da liegen, wo er liegt, nämlich im Flash! ABER ????? Forth benötigt doch einen gewissen RAM--Bereich, um seine Verwaltungsaufgaben erledigen zu können?? Wo es seine Wortliste absetzen kann?? Das ist absolut richtig, jedoch die Art und Weise wie, können wir unseren eigenen Vorstellungen anpassen. \section{Die Wortliste intern oder extern?} Basis unseres Forth--Systems ist die Wortliste. Wenn wir mit einer Colon--Definition ein neues Wort erzeugen, müssen, wie wir wissen, die Verweise auf das, was dieses neue Wort tun soll, im RAM Platz finden. Und zwar ganz oben im schon bestehenden Teil. Und wenn wir nun dafür sorgen könnten, dass sich in der Wortliste, die im RAM liegt, nur ein einziges Wort befindet und dass alle anderen vorgefertigten Worte im Flash abgelegt sind? Wir müssen dann dafür sorgen, dass der Verweis auf die internen Worte richtig arbeitet. Und dass wir die internen Worte mit der Suchfunktion \inst{FIND} finden können. Hier drängt sich wieder der Unterschied zu einem System auf, das mit einem 8052--Prozessor arbeitet: ROM (beim AVR der Flash--Speicher) und RAM können wir dort im Gesamtspeicher aufeinanderlegen. Für die Suchfunktion besteht dann kein Unterschied, ROM-- und RAM--Bereich sind dann praktisch dasselbe. Schön und gut, aber wir haben einen AVR--Prozessor, und der kann das nun einmal nicht. Was er aber sehr wohl kann, ist die Behandlung der beiden Speicherarten auf eine andere Weise. Im Befehlssatz des AVRs gibt es für das Einholen von Daten zwei verschiedene Maschinenbefehle (siehe Befehlssatz in der AVR--Dokumentation auf der Atmel--Website). \begin{figure*}[t] \begin{center} \includegraphics[width=0.7\textwidth]{2007-Sonderheft-AVR/Teil7-8-Bild4} \caption{\label{minke7:abb4}Speicherplan} \end{center} \end{figure*} \section{Das Anpassen von \inst{FIND}} Wir müssen uns für die Suchfunktion \inst{FIND} etwas überlegen, das deutlich macht, wo \inst{FIND} suchen soll. Man denkt zunächst einmal an ein Software--Flag, das anzeigt, ob die Suche intern, im Flash des Prozessors, vonstattengehen soll, oder extern, im RAM. Alle neu zu machenden Definitionen koppeln wir mit dem Flagstand extern aneinander, und bei allen vorgefertigten Definitionen steht das Flag auf intern. Die Suchfunktion \inst{FIND} muss aus zwei Teilen bestehen; der erste Teil sucht ausschließlich im RAM, der zweite Teil ausschließlich im Flash. Als Trennung zwischen den beiden Teilen könnten wir ein Null--Link gebrauchen, das auf das Ende der Wortliste verweist. Sobald das \inst{FIND} zum ersten Mal auf das Null--Link trifft, muss es auf interne Suche umschalten. Beim zweiten Mal ist das Ende der Wortliste schon erreicht. Auf diese Weise muss eine Forth--Definition gefunden werden können. Nach einigen Software--Experimenten schien das tatsächlich zu funktionieren. Wir begegnen dabei jedoch einem anderen Problem. Das Auffinden eines Wortes ist nur die eine Hälfte des Problems, dessen Ausführung ist die andere Hälfte. Auf die eine oder andere Weise muss man an der Liste von PFAs erkennen können, woraus eine Definition aufgebaut ist, wo sich das Wort befindet. Intern oder extern? Der Schlüssel dazu liegt beim Adresswert. \section{Es musste noch experimentiert werden} Zunächst dachten wir daran, die internen Worte irgendwie zu markieren, beispielsweise durch ein Hochsetzen von Adressbit 15. Bei den externen Worten sollte das Bit zurückgesetzt bleiben. Alle Code--Worte, die mit dem direkten Einholen von Daten aus dem Speicher zu tun haben, müssen dann dieses Bit untersuchen und die richtige Arbeitsweise wählen. Der Gedanke war einfach, die Ausführung nicht ganz so einfach. Beim Ausarbeiten dieser Idee ergab sich jedoch eine andere Lösung: Eine Trennung in Adressbereiche. Wenn wir uns den Speicherplan (siehe Abbildung \ref{minke7:abb4}) genauer betrachten, bemerken wir zwei Dinge: Internes RAM beim MEGA162: Von $0100$ bis $0500$\\ Flash: Die Header im Kernel beginnen bei (ungefähr) $0500$. Was können wir damit anfangen? Nun ja, hier liegt die Basis einer Implementation der Innerhalb/außerhalb--Idee. \section{Die Implementation} Wenn wir nun unterscheiden würden zwischen Bereichen unterhalb der Adresse $0500$ und Bereichen oberhalb $0500$? Unterhalb der Adresse $0500$ entscheiden wir uns für den Zugang zum internen RAM (mit dem Befehl \inst{Ld}) und oberhalb der Adresse $0500$ verwenden wir den speziellen Befehl \inst{Lpm}, um uns den Zugang zum Flash zu sichern. Erst müssen wir jedoch herausfinden, welche Forth--Worte nun eigentlich mit dem Speicher direkt zu tun haben. Worte, die Daten auf dem Stack bearbeiten, fallen nicht hierunter. Die betreffenden \inst{CODE}--Worte sind: \inst{LIT BRANCH NEXT EXECUTE @ C@ CMOVE (FIND) COUNT} und die internen Codeteile der Worte \inst{CONSTANT} und \inst{USER}. Alle diese Worte holen aus der Wortliste etwas herein und stellen dort dann etwas damit an. In allen diesen Worten wurde ein Test eingebaut, der prüft, ob die angebotene Adresse kleiner als $0500$ ist. Wenn ja, dann ist es eine externe Adresse im internen RAM. Wenn nein, dann ist es eine Adresse aus dem vorgefertigten Teil, der nur die Header und Links enthält. Das Ergebnis des Adresstests zeigt an, mit welchem Befehl die entsprechenden Daten hereingeholt werden sollen. Und siehe da: ES FUNKTIONIERT. Das experimentelle Forth läuft auf einem nackten Prozessor! Der einzige Nachteil besteht darin, dass da wenig Platz übrig bleibt, um eigene Definitionen hinzuzufügen. Aber für das eine oder andere Experiment mit einem (selbstgemachten) Hardware--Aufbau ist es prima geeignet. Haben wir keinen Bedarf an der sogenannten Vocabulary--Struktur, dann entfernen wir den dafür vorgesehenen Code und haben ein Simple--Forth übrig, das etwas mehr an Speicher zur Verfügung hat. \section{Ein letzter Stolperstein} Beim Herumexperimentieren kommt man natürlich auch mal in die Situation, dass das System hängen bleibt. Im unserem Fall kann das dann vorkommen, wenn man zu viele Definitionen hinzugefügt hat. Warum nun das schon wieder? Die Antwort ist in der Struktur unseres Forth--Systems (das auf der FIG--Version 1982 beruht) zu suchen. Das System enthält einen Notizblock für interne Zwecke, das \inst{PAD}, das in einem festen Abstand oberhalb des zuletzt definierten Wortes liegt. Wenn man nun zu viele eigene Definitionen erzeugt hat, ist kein Platz mehr da für den \inst{PAD}--Teil. Oberhalb der Adresse $0500$ gibt es einfach keinen Speicher mehr! Forth kann seinen Verwaltungsaufgaben nicht mehr nachkommen \dots{} und bleibt hängen. Eine einfache Lösung besteht darin, dass man bei den \inst{USER}--Konstanten, die das gesamte Forth--System beschreiben, extra Platz für das \inst{PAD} freimacht und an eine niedrigere Adresse die Warnung Wortliste voll legt. \section{Schlusswort} In dieser Artikelserie haben wir versucht, ein Bild davon zu skizzieren, wie man ein kleines Forth--System von nichts her aufbauen kann, welche Punkte dabei zu beachten sind, welche Wahlmöglichkeiten man hat und welche Entscheidungen man bei diesem Vorhaben treffen muss. Als Ergebnis liegt ein bestens arbeitendes experimentelles Forth auf einem Atmel--AVR--Prozessor (mit oder ohne RAM) vor. Wollen Sie mithelfen, es weiter zu verbessern? Ideen sind sehr willkommen! \end{multicols} \vfill \begin{center} \includegraphics[width=0.7\textwidth]{2007-Sonderheft-AVR/ATmega162-Board} \caption{Das Zielsystem mit ATmega161} \end{center} \vfill \end{document} %\pagebreak % %\phantom{x} % %\pagebreak