MODULE aa001; (* This software is released under the rules of the GNU GPL. Please feel free to improve this software, if needed. You may even port this source to a lesser language like C. This software comes without any warranty of any kind. *) (* version aaXXX is an assembler for Atmel AVR processors *) (* 0.01 : Try to get the framework running : , 2008 *) IMPORT ASCII, Arguments, InOut, MemPools, NumConv, Strings, SYSTEM, TextIO; TYPE Identifier = ARRAY [0..31] OF CHAR; SymbolType = (Label, Constant, Alias, Variable); Lstat = (comment, label, space); SymbolPtr = POINTER TO SymbolNode; PortPtr = POINTER TO PortNode; BitNamesPtr = POINTER TO BitNamesNode; OpcodePtr = POINTER TO OpcodeNode; OpcodeNode = RECORD name : Identifier; minClass, opcodeCode : CARDINAL; next : OpcodePtr END; SymbolNode = RECORD name : Identifier; type : SymbolType; next : SymbolPtr; value : CARDINAL END; (* type value ------- -------- Label Address in FLASH Constant Value Alias Register number (may be ambiguous) Variable Address in RAM *) PortNode = RECORD number : CARDINAL; Name : Identifier; next : PortPtr END; BitNamesNode = RECORD RegName : Identifier; BitNr : CARDINAL; BitName : Identifier; next : BitNamesPtr END; VAR storage : Arguments.ArgTable; listFile, lstFile, inFile, outFile : TextIO.File; token, Source, Target, path, option : Identifier; PC, FlashRam, Eeprom, Sram, RAMstart, inRAM, cpuClass, line, column : CARDINAL; DecodeNext, ok, BigFlash : BOOLEAN; Symbols, Opcodes, PortNames, BitNames : MemPools.MemPool; currentType : SymbolType; firstOpcode, thisOpcode : OpcodePtr; firstPort : PortPtr; firstBitName : BitNamesPtr; firstSymbol, thisSymbol : SymbolPtr; PROCEDURE UserMessage (nr : CARDINAL); BEGIN CASE nr OF 0 : InOut.WriteCard (line, 5); InOut.Write (':'); InOut.WriteCard (column, 5) | 1 : InOut.WriteString ("Syntax : aa infile [-t processortype] [-v]") | 2 : InOut.WriteString ("aa version 0.01, May 2008") | 3 : InOut.WriteString ("Could not open "); InOut.WriteString (path) | 4 : InOut.WriteString ("Error in Configuration files. Aborting.") | 5 : InOut.WriteString ("Could not open source file. Aborting.") | 6 : InOut.WriteString ("No source file defined. Aborting.") | 7 : InOut.WriteString ("Could not create destination file.") | 8 : InOut.WriteString ("Premature end of file encountered. Aborting.") | 9 : InOut.WriteString ("Error in "); InOut.WriteString (path) | 10 : InOut.WriteString ("Illegal IO port number; ignoring.") | 11 : InOut.WriteString ("Duplicate opcode found: "); InOut.WriteString (option); InOut.WriteString (". Aborting.") | 12 : InOut.WriteString ("Error in number in "); UserMessage (0) | 13 : InOut.WriteString ("Duplicate symbol in "); UserMessage (0) | 14 : InOut.WriteString ("Missing '=' token in "); UserMessage (0) | 15 : InOut.WriteString ("Missing ':' token in "); UserMessage (0) | 16 : InOut.WriteString ("") | 17 : InOut.WriteString ("") | 18 : InOut.WriteString ("") | 19 : InOut.WriteString ("") ELSE InOut.WriteString ("Something weird happened while processing line"); UserMessage (0) END; InOut.WriteLn END UserMessage; PROCEDURE StoreOpcode (str : Identifier; cat, opc : CARDINAL) : BOOLEAN; VAR thisOne, nextOne : OpcodePtr; BEGIN thisOne := firstOpcode; LOOP IF Strings.StrEq (thisOne^.name, str) THEN RETURN FALSE END; IF thisOne^.next = NIL THEN EXIT END; thisOne := thisOne^.next END; MemPools.PoolAllocate (Opcodes, nextOne, SYSTEM.TSIZE (OpcodeNode)); thisOne^.next := nextOne; WITH nextOne^ DO name := str; minClass := cat; opcodeCode := opc; next := NIL END; RETURN TRUE END StoreOpcode; PROCEDURE FindOpcode (str : Identifier) : BOOLEAN; VAR thisOne, nextOne : OpcodePtr; BEGIN thisOne := firstOpcode; LOOP IF Strings.StrEq (thisOne^.name, str) THEN thisOpcode := thisOne; RETURN TRUE END; IF thisOne^.next = NIL THEN EXIT END; thisOne := thisOne^.next END; RETURN FALSE END FindOpcode; PROCEDURE StoreSymbol (str : Identifier) : BOOLEAN; VAR thisOne, nextOne : SymbolPtr; BEGIN thisOne := firstSymbol; LOOP IF Strings.StrEq (thisOne^.name, str) THEN RETURN FALSE END; IF thisOne^.next = NIL THEN EXIT END; thisOne := thisOne^.next END; MemPools.PoolAllocate (Symbols, nextOne, SYSTEM.TSIZE (SymbolNode)); thisOne^.next := nextOne; WITH nextOne^ DO name := str; type := currentType; next := NIL; END; thisSymbol := nextOne; RETURN TRUE END StoreSymbol; PROCEDURE FindSymbol (str : Identifier) : BOOLEAN; VAR thisOne, nextOne : SymbolPtr; BEGIN thisOne := firstSymbol; LOOP IF Strings.StrEq (thisOne^.name, str) THEN thisSymbol := thisOne; RETURN TRUE END; IF thisOne^.next = NIL THEN EXIT END; thisOne := thisOne^.next END; RETURN FALSE END FindSymbol; PROCEDURE StorePort (str : Identifier; nr : CARDINAL) : BOOLEAN; VAR this, prev, new : PortPtr; result : INTEGER; BEGIN this := firstPort; prev := NIL; LOOP result := Strings.compare (this^.Name, str); IF result = 0 THEN RETURN FALSE END; IF result = 1 THEN EXIT END; prev := this; this := this^.next END; MemPools.PoolAllocate (PortNames, new, SYSTEM.TSIZE (PortNode)); new^.Name := str; new^.number := nr; new^.next := this; IF prev = NIL THEN firstPort := new ELSE prev^.next := new END; RETURN TRUE END StorePort; PROCEDURE FindPort (n : CARDINAL; VAR str : Identifier); VAR ThisOne : PortPtr; BEGIN ThisOne := firstPort; LOOP IF ThisOne^.number = n THEN str := ThisOne^.Name; EXIT ELSE ThisOne := ThisOne^.next END; IF Strings.StrEq (ThisOne^.Name, "|") THEN str := "Reserved"; EXIT END; END END FindPort; PROCEDURE StoreBitName (port : Identifier; bit : CARDINAL; name : Identifier); VAR this, new, prev : BitNamesPtr; BEGIN this := firstBitName; prev := NIL; LOOP IF Strings.compare (this^.RegName, port) >= 0 THEN EXIT END; prev := this; this := this^.next END; MemPools.PoolAllocate (BitNames, new, SYSTEM.TSIZE (BitNamesNode)); WITH new^ DO RegName := port; BitNr := bit; BitName := name; next := this END; IF prev = NIL THEN firstBitName := new ELSE prev^.next := new END END StoreBitName; PROCEDURE FindBitName (reg : Identifier; bit : CARDINAL; VAR str : Identifier); VAR ThisOne : BitNamesPtr; result : INTEGER; BEGIN ThisOne := firstBitName; LOOP result := Strings.compare (ThisOne^.RegName, reg); IF result = 0 THEN LOOP IF bit = ThisOne^.BitNr THEN str := ThisOne^.BitName; RETURN ELSE ThisOne := ThisOne^.next END; IF Strings.StrEq (reg, ThisOne^.RegName) = FALSE THEN EXIT END END ELSIF result = -1 THEN ThisOne := ThisOne^.next ELSE EXIT END END; str [0] := CHR (bit + ORD ('0')); str [1] := 0C END FindBitName; PROCEDURE ConfigureCPU () : BOOLEAN; VAR nr : CARDINAL; PortName, BitName : Identifier; BEGIN InOut.WriteString ("Assembling source "); InOut.WriteString (Source); InOut.WriteString (" for target processor "); InOut.WriteString (Target); InOut.WriteLn; TextIO.OpenInput (inFile, Target); (* Try to open it in current directory *) IF TextIO.Done () = FALSE THEN path := "/usr/local/AVR/"; Strings.Append (path, Target); TextIO.OpenInput (inFile, path); (* Try to open it in /usr/local/AVR *) IF TextIO.Done () = FALSE THEN UserMessage (3); InOut.WriteString ("Choosing ATmega8515 instead."); InOut.WriteLn; InOut.WriteBf; path := "/usr/local/AVR/ATmega8515"; (* Otherwise use the default CPU *) TextIO.OpenInput (inFile, path); IF TextIO.Done () = FALSE THEN UserMessage (3); RETURN FALSE (* If all fails, use your ejectionseat *) END END END; (* The CFG file is now open *) REPEAT TextIO.GetString (inFile, option); IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); RETURN FALSE END UNTIL Strings.StrEq (option, 'BEGIN'); LOOP IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); RETURN FALSE END; TextIO.GetString (inFile, option); IF Strings.StrEq (option, 'END') = TRUE THEN EXIT ELSIF Strings.StrEq (option, 'FLASH') = TRUE THEN TextIO.GetCard (inFile, FlashRam) ELSIF Strings.StrEq (option, 'EEPROM') = TRUE THEN TextIO.GetCard (inFile, Eeprom) ELSIF Strings.StrEq (option, 'SRAM') = TRUE THEN TextIO.GetCard (inFile, Sram) ELSIF Strings.StrEq (option, 'RAMSTART') = TRUE THEN TextIO.GetCard (inFile, RAMstart) ELSIF Strings.StrEq (option, 'COMPILER') = TRUE THEN REPEAT TextIO.GetString (inFile, option) UNTIL Strings.StrEq (option, 'END') ELSIF Strings.StrEq (option, 'PORTS') = TRUE THEN LOOP TextIO.GetString (inFile, option); IF Strings.StrEq (option, 'END') = TRUE THEN EXIT END; NumConv.Str2Num (nr, 16, option, ok); TextIO.GetString (inFile, PortName); IF NOT ok THEN UserMessage (10) ELSE ok := StorePort (PortName, nr) END END ELSIF Strings.StrEq (option, 'BITS') = TRUE THEN LOOP TextIO.GetString (inFile, PortName); IF Strings.StrEq (PortName, 'END') = TRUE THEN EXIT END; nr := 7; LOOP TextIO.GetString (inFile, BitName); IF BitName [0] # '-' THEN StoreBitName (PortName, nr, BitName) END; IF nr = 0 THEN EXIT ELSE DEC (nr) END END END ELSE InOut.WriteString ('Invalid parameter in '); InOut.WriteString (path); InOut.Write ('.'); InOut.WriteLn; RETURN FALSE END END; TextIO.Close (inFile); IF FlashRam > 4096 THEN BigFlash := TRUE ELSE BigFlash := FALSE END; RETURN TRUE END ConfigureCPU; PROCEDURE ConfigureOps () : BOOLEAN; VAR nr, cat, opc : CARDINAL; BEGIN path := "Opcodes"; TextIO.OpenInput (inFile, path); (* Try to open Opcodes file locally *) IF TextIO.Done () = FALSE THEN path := "/usr/local/AVR/Opcodes"; TextIO.OpenInput (inFile, path); (* Or load systemwide version *) IF TextIO.Done () = FALSE THEN UserMessage (3); RETURN FALSE END END; REPEAT TextIO.GetString (inFile, option); IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); TextIO.Close (inFile); RETURN FALSE END UNTIL Strings.StrEq (option, 'PROCESSORS'); LOOP IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); TextIO.Close (inFile); RETURN FALSE END; TextIO.GetString (inFile, option); IF Strings.StrEq (option, 'END') = TRUE THEN EXIT END; IF Strings.StrEq (option, Target) = TRUE THEN TextIO.GetCard (inFile, cpuClass) END END; IF cpuClass = 0 THEN InOut.WriteString ("Target CPU not found in Opcode list, assuming ATmega8515."); InOut.WriteLn; cpuClass := 3 END; REPEAT TextIO.GetString (inFile, option); IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); TextIO.Close (inFile); RETURN FALSE END UNTIL Strings.StrEq (option, 'OPCODES'); LOOP IF TextIO.EOF (inFile) = TRUE THEN UserMessage (9); TextIO.Close (inFile); RETURN FALSE END; TextIO.GetString (inFile, option); IF Strings.StrEq (option, 'END') = TRUE THEN EXIT END; TextIO.GetCard (inFile, cat); TextIO.GetCard (inFile, opc); IF StoreOpcode (option, cat, opc) = FALSE THEN UserMessage (11); TextIO.Close (inFile); RETURN FALSE END END; TextIO.Close (inFile); RETURN TRUE END ConfigureOps; PROCEDURE GetChar (VAR ch : CHAR); BEGIN TextIO.GetChar (inFile, ch); IF TextIO.EOF (inFile) = TRUE THEN UserMessage (8); HALT END; IF ch = ASCII.LF THEN column := 1; INC (line) ELSIF ch = ASCII.TAB THEN ch := ' ' END END GetChar; PROCEDURE Sync; VAR chr : CHAR; BEGIN REPEAT GetChar (chr) UNTIL chr = ASCII.LF END Sync; PROCEDURE GetToken; VAR chr : CHAR; i : CARDINAL; BEGIN i := 0; REPEAT GetChar (chr) UNTIL chr > ' '; REPEAT token [i] := chr; INC (i); GetChar (chr) UNTIL (chr = ' ') OR (chr = ASCII.LF); IF i <= HIGH (token) THEN token [i] := 0C END END GetToken; PROCEDURE GetCard (VAR nr : CARDINAL) : BOOLEAN; VAR chr : CHAR; Base, i : CARDINAL; buff : Identifier; BEGIN i := 0; REPEAT GetChar (chr) UNTIL chr > ' '; IF chr = '$' THEN Base := 16; GetChar (chr) ELSIF chr = '#' THEN Base := 2; GetChar (chr) ELSIF chr = '%' THEN Base := 8; GetChar (chr) ELSE Base := 10 END; REPEAT buff [i] := chr; GetChar (chr); INC (i); IF chr = '.' THEN GetChar (chr) END UNTIL chr = ' '; IF i <= HIGH (buff) THEN buff [i] := 0C END; NumConv.Str2Num (nr, Base, buff, ok); IF NOT ok THEN UserMessage (12); Sync END END GetCard; PROCEDURE doCONST; VAR done : BOOLEAN; nr : CARDINAL; BEGIN currentType := Constant; REPEAT GetToken; IF Strings.StrEq (token, 'END') THEN done := TRUE ELSE IF StoreSymbol (token) = TRUE THEN GetToken; IF Strings.StrEq (token, '=') THEN GetCard (nr); thisSymbol^.value := nr ELSE UserMessage (14) END ELSE UserMessage (13) END END; Sync UNTIL done END doCONST; PROCEDURE doMESS; BEGIN END doMESS; PROCEDURE doDATA; VAR delta : CARDINAL; BEGIN currentType := Variable; REPEAT GetToken; IF Strings.StrEq (token, 'END') THEN done := TRUE ELSE IF StoreSymbol (token) = TRUE THEN GetToken; IF Strings.StrEq (token, ':') THEN thisSymbol^.value := inRAM; GetCard (delta); INC (inRAM, delta) ELSE UserMessage (15) END ELSE UserMessage (13) END END; Sync UNTIL done END doDATA; PROCEDURE doCOMMENT; VAR chr : CHAR; endToken : IDENTIFIER; BEGIN GetToken; endToken := token; REPEAT GetToken; IF Strings.StrEq (token, COMMENT) THEN doCOMMENT END; UNTIL token = endToken END doCOMMENT; PROCEDURE isPseudo (str : IDENTIFIER) : BOOLEAN; BEGIN IF Strings.StrEq (str, 'ORIGIN') THEN GetCard (PC) ELSIF Strings.StrEq (str, 'CONSTANTS') THEN doCONST ELSIF Strings.StrEq (str, 'COMMENT') THEN doCOMMENT ELSIF Strings.StrEq (str, 'DATA') THEN doDATA ELSIF Strings.StrEq (str, 'MESSAGES') THEN doMESS END END isPseudo; PROCEDURE GetLabel (VAR str : Identifier) : Lstat; VAR ch : CHAR; i : CARDINAL; BEGIN i := 0; GetChar (ch); IF ch = ' ' THEN RETURN space ELSIF ch = ';' THEN Sync; RETURN comment ELSE WHILE ch > ' ' DO str [i] := ch; INC (i); GetChar (ch) END; str [i] := 0C END; RETURN label END GetLabel; PROCEDURE GetComment (VAR str : Identifier); BEGIN END GetString; PROCEDURE OpenFiles; VAR filename : Identifier; BEGIN filename := Source; Strings.Append (filename, '.asm'); TextIO.OpenInput (inFile, filename); IF NOT TextIO.Done () THEN UserMessage (5); HALT END; filename := Source; Strings.Append (filename, '.lst'); TextIO.OpenOutput (lstFile, filename); IF NOT TextIO.Done () THEN UserMessage (7); HALT END; filename := Source; Strings.Append (filename, '.out'); TextIO.OpenOutput (outFile, filename); IF NOT TextIO.Done () THEN UserMessage (7); HALT END; TextIO.PutString (lstFile, "Assembly listing"); TextIO.PutLn (lstFile); TextIO.PutLn (lstFile); TextIO.PutString (lstFile, "Source : "); TextIO.PutString (lstFile, Source); TextIO.PutString (lstFile, " assembling for target processor "); TextIO.PutString (lstFile, Target); TextIO.PutLn (lstFile); TextIO.PutLn (lstFile); TextIO.PutLn (lstFile); TextIO.PutLn (lstFile) END OpenFiles; PROCEDURE ListOps; VAR thisOne : OpcodePtr; BEGIN TextIO.PutString (listFile, "Opcode list"); TextIO.PutLn (listFile); thisOne := firstOpcode; LOOP TextIO.PutString (listFile, thisOne^.name); TextIO.PutLn (listFile); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "minClass : "); TextIO.PutCard (listFile, thisOne^.minClass, 4); TextIO.PutLn (listFile); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "opcodeCode : "); TextIO.PutCard (listFile, thisOne^.opcodeCode, 4); TextIO.PutLn (listFile); IF thisOne^.next = NIL THEN EXIT ELSE thisOne := thisOne^.next END END; TextIO.PutLn (listFile); TextIO.PutLn (listFile) END ListOps; PROCEDURE ListSyms; VAR thisOne : SymbolPtr; BEGIN TextIO.PutString (listFile, "Symboltable list"); TextIO.PutLn (listFile); thisOne := firstSymbol; LOOP TextIO.PutString (listFile, thisOne^.name); TextIO.PutLn (listFile); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "Type : "); CASE thisOne^.type OF Label : option := "Label " | Constant : option := "Constant " | Alias : option := "Alias " | Variable : option := "Variable " END; TextIO.PutString (listFile, option); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "Value : "); TextIO.PutCard (listFile, thisOne^.value, 6); TextIO.PutLn (listFile); IF thisOne^.next = NIL THEN EXIT ELSE thisOne := thisOne^.next END END; TextIO.PutLn (listFile); TextIO.PutLn (listFile) END ListSyms; PROCEDURE ListPorts; VAR thisOne : PortPtr; BEGIN TextIO.PutString (listFile, "Portnames list"); TextIO.PutLn (listFile); thisOne := firstPort; LOOP TextIO.PutString (listFile, thisOne^.Name); TextIO.PutLn (listFile); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "number : "); TextIO.PutCard (listFile, thisOne^.number, 4); TextIO.PutLn (listFile); IF thisOne^.next = NIL THEN EXIT ELSE thisOne := thisOne^.next END END; TextIO.PutLn (listFile); TextIO.PutLn (listFile) END ListPorts; PROCEDURE ListNames; VAR thisOne : BitNamesPtr; BEGIN TextIO.PutString (listFile, "Bitnames list"); TextIO.PutLn (listFile); thisOne := firstBitName; LOOP TextIO.PutString (listFile, thisOne^.RegName); TextIO.PutLn (listFile); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "Bit nr : "); TextIO.PutCard (listFile, thisOne^.BitNr, 4); TextIO.PutChar (listFile, 11C); TextIO.PutString (listFile, "Bit name : "); TextIO.PutString (listFile, thisOne^.BitName); TextIO.PutLn (listFile); IF thisOne^.next = NIL THEN EXIT ELSE thisOne := thisOne^.next END END; TextIO.PutLn (listFile); TextIO.PutLn (listFile) END ListNames; PROCEDURE ListLists; VAR filename : Identifier; BEGIN filename := Source; Strings.Append (filename, '.list'); TextIO.OpenOutput (listFile, filename); ListOps; ListSyms; ListPorts; ListNames; TextIO.Close (listFile) END ListLists; PROCEDURE Assemble; BEGIN END Assemble; PROCEDURE CloseFiles; BEGIN TextIO.Close (inFile); TextIO.Close (lstFile); TextIO.Close (outFile); MemPools.KillPool (PortNames); MemPools.KillPool (BitNames); MemPools.KillPool (Opcodes); MemPools.KillPool (Symbols) END CloseFiles; PROCEDURE Init; VAR count, i : SHORTCARD; BEGIN MemPools.NewPool (PortNames); MemPools.NewPool (BitNames); MemPools.NewPool (Symbols); MemPools.NewPool (Opcodes); MemPools.PoolAllocate (PortNames, firstPort, SYSTEM.TSIZE (PortNode)); WITH firstPort^ DO Name := "|"; number := 5000; next := NIL END; MemPools.PoolAllocate (BitNames, firstBitName, SYSTEM.TSIZE (BitNamesNode)); WITH firstBitName^ DO RegName := "|"; BitNr := 50000; BitName := "|"; next := NIL END; MemPools.PoolAllocate (Symbols, firstSymbol, SYSTEM.TSIZE (SymbolNode)); WITH firstSymbol^ DO name := "|"; value := MAX (CARDINAL); next := NIL END; MemPools.PoolAllocate (Opcodes, firstOpcode, SYSTEM.TSIZE (OpcodeNode)); WITH firstOpcode^ DO name := "|"; minClass := 0; opcodeCode := 1024; next := NIL END; line := 1; column := 1; PC := 0; RAMstart := 96; Strings.EmptyString (Source); Target := "ATmega8515"; Arguments.GetArgs (count, storage); IF count = 1 THEN UserMessage (2); UserMessage (1); HALT END; i := 1; LOOP IF i >= count THEN EXIT END; Strings.Assign (option, storage^ [i]^); IF Strings.StrEq (option, '-t') THEN INC (i); IF i = count THEN UserMessage (11); InOut.WriteLn; UserMessage (1); UserMessage (2); HALT END; Strings.Assign (Target, storage^ [i]^) ELSIF Strings.StrEq (option, '-v') THEN UserMessage (2) ELSE Strings.Assign (Source, option) END; INC (i) END; IF Source [0] = 0C THEN UserMessage (6); HALT END; PC := 0; cpuClass := 0 END Init; BEGIN Init; IF (ConfigureCPU () = FALSE) OR (ConfigureOps () = FALSE) THEN UserMessage (4); HALT END; UserMessage (2); OpenFiles; Assemble; CloseFiles; ListLists END aa001.