% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol,babel} \usepackage{xspace} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} %\newcommand{\code}[1]{\texttt{#1}} %\newcommand{\ret}{\textsf{$<$ret$>$}\xspace} %\newcommand{\ret}{$\hookleftarrow$\xspace} \renewcommand{\reftextbefore}{auf der vorherigen Seite} \renewcommand{\reftextfacebefore}{auf der gegenüberliegenden Seite} \renewcommand{\reftextafter}{auf der nächsten Seite} \renewcommand{\reftextfaceafter}{auf der gegenüberliegenden Seite} \renewcommand{\figurename}{Bild} \begin{document} \title{amforth XXL} \author{Matthias Trute} \begin{document} \maketitle Amforth ist ein 16--Bit--Forth für die 8--Bit--Mikrocontrollerserie Atmega der Firma Atmel. Mit einer Cellsize von 16 Bit scheinen die Grenzen deutlich zu sein: 64 KB Adressraum. Doch da diese Controller einen getrennten Daten-- und Programmadressraum haben (\emph{Harvard Architektur}\/) steht der 64--KB--Adressraum zweimal zur Verfügung. \begin{figure*}[b] \vspace*{0.2ex} \begin{center} \includegraphics[width=0.45\textwidth]{2010-0203/img_4472} \end{center} \caption{\label{amforth-xxl:Bild1}AVR--Entwicklungsmodul mit 128 KB ext.\ SRAM und ATMEGA2561 [3]} \end{figure*} \begin{multicols}{2} Tatsächlich läuft amforth jedoch auch auf Systemen, die deutlich mehr als dies haben. Und dieses Mehr steht den Programmen auch zur Verfügung, ohne dass man die Programme umschreiben müsste. Bei alledem belegt das Kernsystem nicht mehr Platz als auf den kleinen Systemen. \begin{verbatim} amforth 3.9 ATmega2561 14745 kHz > : hallo ." Hallo " ; ok > hallo Hallo ok > words hallo d- d+ dinvert int@ int! is Rdefer Edefer show-wordlist ... > unused decimal u. 57537 ok > \end{verbatim} Um das zu erreichen, hat schon der Hersteller einige Entscheidungen beim Design der Controller getroffen, die amforth ausnutzt. Sie betreffen den Programmspeicher, der als selbst--reprogrammierbarer Flash ausgeführt ist und bei amforth das gesamte Dictionary enthält. \section{Die Speicheraufteilung} Amforth als 16--Bit--System ist natürlich begrenzt auf die Möglichkeiten, die 16 Bit bieten: 65536 Adressen. Beim Flash heisst das ungewohnterweise 128 KB. Der Grund ist folgender: Der Flash beinhaltet normalerweise den ausführbaren Code. Die Maschineninstruktionen der Atmegas sind nun immer 16 Bit oder Vielfache davon groß (RISC lässt grüßen). Atmel hat das so implementiert, dass hinter jeder Adresse 2 Bytes liegen. Dass Programme zwei Instruktionen benötigen \texttt{(lpm Z+)} und solcherart doch eine Art byte--basierte Adressierung zum Einsatz kommt, zählt wohl zu den Seltsamkeiten der Plattform\ldots Damit kann das Dictionary 128 KB groß werden. Bei Systemen, die noch mehr Flash haben, ergibt sich ein Dilemma: Der nutzbare Speicher ist kleiner als der vorhandene. Soll heißen: Es liegt Flashspeicher brach. Zum anderen ist der Sonderbereich, der für das Selbstprogrammieren des Flashes zu nutzen ist, jenseits der Adressgrenze für die zahlreich genutzten \texttt{JMP/CALL}--Befehle. Letzteres ist einfach zu lösen: Es gibt einen neuen Befehl: \texttt{RAMPZ}. Er ist zwar deutlich umständlicher zu benutzen und damit langsamer, da aber der Flash--Programmierbefehl ohnehin 2--3 Millisekunden benötigt, spielt das keine Rolle. Um ersteres Dilemma zu lösen, schließlich bezahlt niemand für etwas, was er nicht nutzen kann, gibt es mehrere Ansätze, die derzeit noch nicht umgesetzt sind, aber sicher über kurz oder lang nutzbar sein dürften: Zum einen kann man Worte definieren, die zur Adressierung nicht eine, sondern zwei Zellen einsetzen. Dass das bislang nicht umgesetzt ist, ist auch eine Frage der Wortwahl: Wie sollte so ein Wort heißen? \texttt{id@ ( d -{}- n )} bzw. \texttt{id! ( n d -{}- )}?% \footnote{\texttt{i!} ist das amforth--Wort, um in den Instruktionsspeicher zu schreiben. Hierbei ist die Adresse eine Zelle groß, also 16 Bit. \texttt{id@} liefert also keine doppelt--genaue Zahl, sondern nimmt eine solche als Adresse.} \footnote{Interessanterweise gibt es ein analoges Dilemma auch beim Zugriff auf SD--Karten: Das \texttt{BLOCKS} Wordset definiert Zugriffsworte, die genau eine Zelle als Blocknummer beinhalten. Dummerweise gibt es so kleine SD Cards aber gar nicht mehr\ldots\ wenn man ein 16--Bit--Forth betrachtet.} Der zweite Ansatz geht auf eine Idee von Michael Kalus zurück: Flash als Blockdevice ansprechen. Und wieder gibt es zwei Varianten: Standard--Forth Blöcke von 1 KB oder die device--spezifische Pagesize nutzen. Relativer Nachteil der Standard--Blöcke ist, dass sie recht viel vom RAM für Buffer belegen, der immer knapp ist. Dies ist bei der zweiten Variante deutlich entspannter, dafür aber auch anspruchsvoller bei der Nutzung. Andererseits stehen beim Atmega2561 8 KB interner RAM zur Verfügung. \section{Technisches} Wer den Quellcode analysiert, wird feststellen, dass die Unterstützung für große (Flash--) Speichermengen in mehreren Stufen erfolgt. Die erste Stufe sind die 128--KB--Systeme. Hier kommt ein zusätzliches Adressregister RAMPZ zum Einsatz. Es erweitert das normalerweise genutzte Z--Registerpaar um ein weiteres Byte und stellt so die komplette Adresse bereit. Dieses Registertriplett wird von den Befehlen \texttt{elpm} und \texttt{spm} genutzt, um den Flashspeicher zu adressieren. Im Quelltext wird das alles in den Assemblermakros \texttt{readflashcell} und \texttt{writeflashcell} gekapselt. In diesen Macros ist auch die eigentümliche interne Zugriffsmethode auf Byteadressen versteckt. Aus Performancesicht sind große Atmegas bei gleicher Taktfrequenz langsamer als kleinere. Kleine Systeme benötigen 4 Instruktionen, um eine Flashzelle zu lesen. Große Systeme müssen 7 Befehle ausführen, um das Gleiche zu leisten. Da jedoch zusätzlich immer die Stackoperationen und der innerere Interpreter involviert sind, ist die prozentuale Einbuße, bezogen auf das Gesamtsystem, deutlich geringer. In der Praxis wird man wohl einen etwas schnelleren Quarz verbauen\dots \begin{small} \hfil \begin{minipage}[t]{0.4\columnwidth} \begin{verbatim} .macro readflashcell lsl zl rol zh lpm @0, Z+ lpm @1, Z+ .endmacro .macro writeflashcell lsl zl rol zh .endmacro \end{verbatim} \end{minipage} \hfil \begin{minipage}[t]{0.4\columnwidth} \begin{verbatim} .macro readflashcell clr temp7 lsl zl rol zh rol temp7 out RAMPZ, temp7 elpm @0, Z+ elpm @1, Z+ .endmacro .macro writeflashcell clr temp7 lsl zl rol zh rol temp7 out RAMPZ, temp7 .endmacro \end{verbatim} \end{minipage} \hfil \end{small}\medskip Der scheinbar fehlende \texttt{spm}--Befehl im writeflashmacro ist an anderer Stelle enthalten. Der zweite Schritt sind Systeme, die mehr als 128 KB haben. Hierzu zählt interessanterweise auch der ATXmega128. Diese Systeme sind erkennbar am 24 Bit (3 Byte) großen Program--Counter--Register. Damit legt z.~B.\ ein CALL 3 Bytes auf den Returnstack, anstelle 2 bei den kleineren Controllern. Bei diesen Controllern liegt die Hürde darin, dass der Bereich, von dem aus das Flash--Selbstprogrammieren erfolgen muss, außerhalb der zulässigen Sprungdistanz der normalerweise genutzten Befehle liegt. \begin{small} \begin{quote} \begin{verbatim} ; ( n addr -- ) Memory ; R( -- ) ; writes a cell in the flash VE_DO_ISTORE: .dw $ff04 .db "(i!)" .dw VE_HEAD .set VE_HEAD = VE_DO_ISTORE XT_DO_ISTORE: .dw PFA_DO_ISTORE PFA_DO_ISTORE: .... ldi zl, byte3(DO_ISTORE_atmega) out_ rampz, zl ldi zh, byte2(DO_ISTORE_atmega) ldi zl, byte1(DO_ISTORE_atmega) eicall ; reaches any address .... ; finally clear the stack loadtos rjmp DO_NEXT .org NRWW_START_ADDR ; way outside of an call/jmp DO_ISTORE_atmega: rcall pageload ; erase page if needed ; it is needed if a bit goes from 0 to 1 com temp4 ..... ret ; return to caller, all done \end{verbatim} % $ \end{quote} \end{small} Damit amforth hier die nötige Flexibilität erhält, wurde das alles entscheidende Wort i! umgebaut, indem es in Assembler komplett neu geschrieben wurde. Das brachte neben einigen Bytes Platzersparnis auch die Möglichkeit, den Programmcode unabhängig vom Rest des Systems zu platzieren. Eine Alternative wäre, ein weiteres Forthsystem zu haben, das nur für die Ausführung von \texttt{i!} zuständig ist. Damit rückt auch ein weiteres Ziel in Reichweite: Da es nur wenige Bytes umfasst, kann man eine Einbeziehung in Standard--Bootloader angehen, die dann nicht nur ein neues System einspielen können, sondern darüberhinaus noch eine API für Programme wie amforth anbieten können, um den Flash gezielt ändern zu können. Der Inhalt des Bootloaderbereichs ist für das amforth--System immer read--only. Daraus ergibt sich die Konsequenz, dass amforth diesen Bereich maximal nutzen sollte. Bei den Systemen mit bis zu 128 KB Flash heisst dies: Alles dort ablegen, was irgendwie geht. Die Systeme mit mehr Flash\-speicher sind da exakt entgegengesetzt: Da amforth mit den Standardworten den Bootloaderbereich nicht addressieren kann, darf \em{nichts} dort liegen. Allerdings \em{muss} das Wort \texttt{i!} dort platziert werden, da für die Dauer des Flashprogrammierens unter keinen Umständen auf den RWW--Flash, der das Dictionary enthält, zugegriffen werden darf. Die CPU wird in diesem Fall einfach gestoppt. Um das zu lösen, liegt der Dictionary--Eintrag von \texttt{i!} zwar im normalen Flash, ruft aber Code im Bootloaderbereich auf. Dieser Code kehrt erst dann zurück, wenn die Arbeit getan ist. Wie man an dem obigen Codeschnipsel sehen kann, ist die Implementierung nicht für jedes Wort sinnvoll einzusetzen. \section{Zukünftiges} Prognosen sind schwierig, vor allem, wenn sie die Zukunft betreffen. Die Eckdaten für amforth werden wohl zukünftig so aussehen: Das Dictionary umfasst maximal 128 KB. Jeglicher Flash\-speicher jenseits der 128 KB wird z.B. als Block über eine 16 Bit große Blocknummer angesprochen. Alternativ oder zusätzlich kann ein Wortpaar analog zu \texttt{i@} und \texttt{/i!} existieren, das als Adresse eine doppelt--genaue Zahl hat und so den Zugriff auf den gesamten Flash\-speicher gewährt. \end{multicols} \section{Links} {}[1] \url{http://www.atmel.com/products/AVR/}\\ {}[2] \url{http://amforth.sourceforge.net/}\\ {}[3] Anbieter des Entwicklungsboard: \url{www.alvidi.de} %\end{document}