% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol,babel} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \begin{document} \title{Assembler in amforth} \author{Matthias Trute} \maketitle \section{Zusammenfassung} Amforth verfügt über einen in Forth geschriebenen Assembler, der von Lubos Pekny programmiert und der Community bereitgestellt wurde. Um seine Arbeit zu unterstützen, erhielt das Kernsystem die Worte \verb|code| und \verb|end-code|. Dieser Artikel soll Lust auf mehr machen, ohne alle Details zu beleuchten. \begin{multicols}{2} Jedes Forth braucht einen Assembler. Heißt es. Bei amforth ist der Assembler ein optionales und nachladbares Modul und ist selbst in Forth geschrieben. Das amforth--Kernsystem ist um zwei Worte erweitert worden, die für die Erzeugung der Codeworte und deren Einbindung in den inneren Interpreter zuständig sind: \verb|code| und \verb|end-code|. Jedes dieser Worte benutzt Informationen, die nur zur Übersetzungszeit des Quellcodes portabel verfügbar sind: die Einsprungadresse des inneren Interpreters \verb|NEXT| und das in verschiedenen Definitionsworten genutzte \verb|(DOCREATE)|, mit dem sich der Rumpf eines Dictionaryeintrags erstellen lässt. Alle weiteren Assemblerbefehle sind als Forth--Code umgesetzt. Die Syntax lehnt sich dabei sowohl an die Konventionen des AVR--Assemblers als auch an Forth an. Dieser Artikel beruht auf der Version 1.1 des Assemblers. Hier auch gleich eine Warnung: Der Einsatz des Assemblers erfordert etwas mehr als nur grundlegende Kenntnisse der Mikrocontroller und der Interna von amforth. Der Programmtext ist nicht portabel, etwa zu PC--Forths, und, wenn man nicht aufpasst, noch nicht einmal zwischen verschiedenen AVR--Controllertypen. \section{AVR Atmega} Die AVR--Atmega--Mikrocontroller der Firma Atmel sind kleine System--On--A--Chip--Bausteine die aus einem Kernsystem und einer Anzahl von Hardwaremodulen bestehen. Das Kernsystem umfasst den Registersatz, die Instruktionslogik, RAM, EEPROM und Flashspeicher. Je nach Typ sind unterschiedliche Speichervolumina vorgesehen, wobei RAM und EEPROM eher knapp bemessen sind (wenige KB), Flash dagegen vergleichsweise viel vorhanden sein kann (derzeit bis zu 256KB). Die Architektur hat zudem die Besonderheit, dass direkt ausführbarer Maschinencode ausschließlich im Flash sein kann. Andere Speichertypen können dafür nicht genutzt werden. Der Registersatz besteht aus 32 8--bit--Registern, von denen einige als 16--bit--Registerpaare genutzt werden können. In amforth werden 8 der 32 Register ausdrücklich für den Einsatz in Code--Wörtern bereitgehalten (Im Quellcode als temp1 bis temp8 bezeichnet). Diese können problemlos innerhalb eines Wortes benutzt werden. Alle anderen Register (und dazu gehören auch die 4 Registerpaare) müssen am Ende des Assemblercodes wiederhergestellt werden, sollten sie genutzt werden. Wirklich ungenutzt sind derzeit nur 4 Register, was sich aber im Zuge der bei der Euroforth2008 diskutierten Erweiterungen der Forth--VM ändern kann\footnote{Derzeit sind die A/B--Register implementiert, die X/Y harren noch der weiteren Entwicklung.}. Der Befehlssatz orientiert auf Register/Register--Operationen. Dabei sind die meisten Befehle für alle oder viele Register einsetzbar. Einige wenige Befehle benutzen feststehende Register, etwa die Hardwaremultiplikation oder der Zugriff auf den Flash. An dieses Kernsystem hat Atmel je nach Typ weitere Module angebaut. Dabei sind einige Module bei allen Controllern vorhanden (z.~B.\ serielle Schnittstelle oder A/D--Wandler), andere Module sind nur bei einigen Typen vorhanden (CAN-- oder USB--Schnittstellen, LCD--Ansteuerung). Die Module werden über definierte (leider bei jedem Typ andere) Adressen für die Konfiguration und die Daten angesprochen. Einige Module bieten die Möglichkeit, Interrupts auszulösen, die beim Eintreffen der Interruptbedingung aktiviert werden. Genau diese Interrupts sind neben Geschwindigkeitsoptimierungen der Grund, den Assembler einzusetzen: Nicht alle Interrupts lassen sich mit der in amforth enthaltenen generischen Interrupt--Service--Routine behandeln, da sie spezielle Aktionen ausführen müssen, die der Hardware signalisieren, dass der Interrupt bearbeitet wurde. Unterlässt man diese Signalisierung, löst die Hardware umgehend einen neuen Interrupt aus, was das System faktisch lahmlegt. \section{Syntax} Der Assembler nutzt die Postfix--Notation, die für Forth so typisch ist. Zuerst werden die Operanden angegeben und danach die Kommandos. Die Namen selbst entstammen den AVR--Definitionen. Die Kommandoworte haben ein Komma als Suffix, um zu signalisieren: Hier wird in das Dictionary geschrieben. \begin{center} \begin{tabular}{|c|c|} \hline AVR--Standard & amforth \\ \hline\hline ldi R16, 10 & R16 10 ldi, \\ subi R8, 1 & R8 1 subi, \\ ld R17, Y+ & R17 Y+ ld, \\ \hline \end{tabular} \end{center} Der Assembler schreibt das Ergebnis seiner Arbeit direkt in das Dictionary und setzt den \verb|HERE|--Pointer entsprechend weiter. Bei Sprüngen wird das Sprungziel ggf.\ nachträglich eingearbeitet, die erforderlichen Angaben werden auf dem Datenstack verwaltet. \section{Codeworte} Codewörter hießen früher Primitive und stellen in Maschinencode geschriebene und für den Forthinterpreter nutzbare Worte dar. Ein einfaches Codewort ist das folgende: \begin{quote} \begin{verbatim} code dup -Y R23 st, -Y R22 st, end-code \end{verbatim} \end{quote} Im Beispiel wird ein Dictionaryeintrag \verb|dup| erzeugt, dessen Execution Token auf den eigenen (noch leeren) Datenbereich zeigt. Im Unterschied zu Definitionswort \verb|:| (COLON) wird nicht in den Compile-Modus umgeschaltet, die Assemblerworte werden ganz normal interpretiert. Dabei werden auf dem Datenstack die Operanden zusammengestellt, die die Assemblerworte dann passend in den Flash compilieren. Die Register R22/R23 bilden das Top--Of--Stack--Element des Datenstacks, das Registerpaar Y ist der Datenstackpointer. Die Mnemonic \verb|st| bezeichnet die Storeoperation (Speichern von Registerinhalten in RAM). \verb|-Y| signalisiert einen Auto-Decrement des Y--Registerpaars. Abgeschlossen wird eine code--Definition mit dem Wort \verb|end-code|, das den obligatorischen Sprung in den inneren Interpreter compiliert. \section{Interrupts} Etwas interessanter wird es, wenn man Interruptroutinen im Assembler umsetzen will oder muss. Das kann, wie eingangs erwähnt, erforderlich sein, um innerhalb der Interruptbearbeitung gewisse Hardwareflags zurückzusetzen, was bei in Forth geschriebenen Interruptroutinen nicht möglich ist. Da Interruptroutinen nicht direkt vom Forth-Interpreter aus aufrufbar sein sollen, legt man am einfachsten einen headerlosen Eintrag im Dictionary an, wobei die Startadresse gespeichert werden sollte. \begin{quote} \begin{verbatim} here R18 push, R18 3F in, ... 3F R18 out, R18 pop, reti, 940c 6 i! 7 i! \end{verbatim} \end{quote} Zuerst wird die Startadresse ermittelt (\verb|here|). Anschließend wird der Assemblercode in das Dictionary compiliert und am Ende die eingangs gespeicherte Startadresse als Ziel eines Sprungelements in die Interrupttabelle des Controllers geschrieben. 940c ist die AVR--Codierung für den absoluten Sprungbefehl, die Adresse 6 ist für den Interrupt 3 zuständig. Bei den meisten Atmega--Typen werden zwei Flashzellen für jeden Interruptvektor benötigt: Eine für den Befehlscode (940c) und eine für die Adresse. Bei kleineren Typen (ATmega8) ist nur eine Flashzelle pro Vektor vorgesehen, dann muss man einen relativen Sprung codieren, der in einer Flashzelle sowohl den Befehlscode als auch die Sprungdistanz enthält. \section{Wer nutzt es?} Lubos Pekny selbst hat mit diesem Assembler einen kleinen Stand--Alone--Rechner mit einem 4--zeiligem Text--LCD und einer PS/2--Standard--Tastatur gebaut. Details und ein Video sind im Internet über \url{www.forth.cz/download.html} zu finden. Michael Kalus hat sich die Mühe gemacht, den Assembler systematisch auf Herz und Nieren zu prüfen; Ergebnis: alles funktioniert korrekt. \end{multicols} \end{document}