--------------------------------------------------------------------------- tniASM v0.45 (c) 2000-2011 by The New Image Programmed by Patriek Lesparre http://tniasm.tni.nl/ e-mail: tniasm@tni.nl --------------------------------------------------------------------------- Chapter 1: Introduction ----------------------- 1.1 What is it? tniASM is a cross assembler for Z80, R800 and GBZ80. Some of its features are: - Multi-pass design - Conditional assembly - Local label mechanism - Extensive 32 bit expression parser with precedence levels - Source and binary file inclusion - Nestable block comments - Multi file output and file patching 1.2 Why? Why would anyone write another assembler? Good question! First, I needed to learn C and there's no better way to learn a new language than to write a program in it you need. That brings me to the second reason I wrote tniASM, which is that none of the other cross assemblers I know had the features I wanted/needed. Because of that, and perhaps the fact that I've never written a assembler/compiler before, tniASM is not like other assemblers. It has many peculiarities and frankly, you might find it a bit weird. Nevertheless, it suits my purposes perfectly and I hope it does yours too. 1.3 tniASM and Passes Most assemblers have 2 passes, one to gather information on labels and another to handle forward references and generate the output. tniASM goes about it differently. The output generation is a pass by itself, and before that tniASM will do as many passes as is needed to evaluate all expressions. Basically it means that tniASM will make 2 passes PLUS an extra pass for every level of forward referencing, up to 5 passes total. 1.4 tniASM Assembler Syntax tniASM has its own assembler syntax for the processors it supports. Don't be alarmed though, the changes are minimal. See chapters 2.7 and on for differences between standard rules. 1.5 Using tniASM tniASM is very easy to use. Just type: tniasm filename tniASM will then try to assemble 'filename' and output the generated machine code to 'tniasm.out'. If a filename without extension is given and there's an error, tniASM will add a ".asm" extension and try again. If you want to use a different output filename than 'tniasm.out' and you don't want to use the FNAME pseudo instruction, you can add a second filename to the command line and tniASM will use that as output. 1.5.1 tniASM Output As said in chapter 1.5, tniASM will normally output machine code to a file called 'tniasm.out'. You can change this by using the FNAME instruction (see chapter 2.5.5). tniASM will also output a symbol table file, called 'tniasm.sym', which contains the value of all labels in your program. It's in the form 'label: EQU value' so it can be INCLUDEd in external files directly. Besides these files, tniASM also creates a temporary file called 'tniasm.tmp' strictly for internal use. Chapter 2: The tniASM Language ------------------------------ 2.1 Case-sensitivity In short, there is none. 'LabelA' and 'labela' are exactly the same to tniASM, as are 'ldir' and 'LdiR'. 2.2 Layout of a tniASM Source Line This is a point where tniASM differs a lot from most assemblers. Usually a source line is layed out like this: [label:] [instruction [operands]] [;comment] Which means it allows any combination of the 3 fields 'label', 'instruction' and 'comment'. In a way tniASM uses the same layout, but it allows any number of label definitions and instructions on a single line. Only the comment field can exist only once. The layout is best described: [ [label:] | [instruction [operands]] ]* [;comment] This means that tniASM is perfectly happy to assemble a line like: start: JP begin exit: LD BC,0042h CALL dos begin: LD DE,text In addition to this freedom, tniASM places no restrictions on white space within a line. For example, labels may have white space before them and instructions may have no space before them, which also means instructions can directly follow a label definition. The same counts for comments. 2.2.1 More on Labels A label definition must end in a colon (':'). The length of a label is practically unlimited. Valid characters in labels are letters, numbers, '_', '.', '?', '!', '~', '@', and '#'. The first character of a label may not be a number. It is allowed to define reserved words (like 'pop', 'ld' or 'hl') as labels, but they must be prefixed with '&' when used. So you can code: call &pop {...} pop: pop hl pop de pop bc jp [hl] tniASM also supports local labels. A local label is always local to the previous non-local label in the source code. A label is local when the first character is a '.'. An example will clarify: main: ld b,8 .loop: call doSomething djnz .loop sub: ld b,8 .loop: call doSomething djnz .loop In the above code four seperate labels are defined: "main", "main.loop", "sub", "sub.loop". Because of this behaviour, you can also access local labels outside the scope of the current non-local label. Like so: main: {...} .end: ret sub: {...} jp main.end .end: ret Or the other way around, create labels that are local outside of the scope of the current non-local label: main: ld a,[.value] {...} sub: {...} main.value: db 0 2.2.2 The Line-seperator The '|' character is used as a line seperator. You can use it to have more than one instruction on a line. In fact the line- seperator is not needed most of the time, since tniASM can figure out by itself that the line "add a,a call jump ret" are actually 3 different instructions. Seperating the instructions with '|' is just nicer to look at and perhaps better to understand. However, in a line like "add a,34 and 3 xor 255" tniASM assumes you mean "add a,253" and not 3 seperate instructions. To make sure tniASM generates the code you want, use the line-seperator. 2.2.3 More on Comments As usual the semi-colon (';') is used as the comment-character. It can be placed anywhere on a line and everthing behind it is ignored until a new line. Besides the normal comments, tniASM also supports comment blocks. The '{' and '}' characters respectively mark the beginning and the end of a comment-block. They can be placed anywhere in a file, and everthing between them is ignored. Comment-blocks are nestable with a practically infinite nesting depth. 2.3 Constants There are 3 different kinds of constants in tniASM: numeric, character and string. Since tniASM is a 32 bit assembler, constants are 32 bit signed integer values, ranging from -2147483648 (80000000h) to 2147483647 (7FFFFFFFh). 2.3.1 Numeric Constants Numeric constants can be represented in decimal, hexadecimal, binary or octal. The supported forms are as follows: Decimal : 123 123d Hexadecimal: 1234h (cannot start with a letter, use '0ABCDh' etc.) $1234 0x1234 Binary : 11100100b (may contain white space, ie. '1110 0100 b') %11100100 ( " " " " " '% 1110 0100') Octal : 123o 123q 2.3.2 String Constants A string constant is anything between single or double qoutes larger than 4 characters. They're used in commands like DB/DW, INCLUDE and FNAME. String constants can not be used in regular expressions. example: DB "a 'double-qouted' string can contain single qoutes" DB 'and a "single-qouted" string can contain double qoutes' 2.3.3 Character Constants Character constants follow the same rules as strings, except that they can be used in expressions. They can be up to 4 characters in size, and are stored low-byte first. The constant 'A' is thus handled as 41h, 'AB' as 4241h (or 41h,42h), 'ABC' as 434241h (41h,42h,43h) and 'ABCD' as 44434241h (41h,42h,43h,44h). In DB/DW, character constants are always considered to be a string constant, except inside an expression. example: DB 'abcd' ; is 'a','b','c','d'. DB +"abcd",1+'a' ; is 'a','b'. Since both strings are inside ; of an expression, they're treated as a ; character constant. DB 'a'+1 ; is an error, because 'a' is not considered ; to be inside the expression. DB ('abcd' >> 8) ; is 'b' 2.4 Expressions Expressions are evaluated in 32 bit signed integer arithmetic. An expression consists of one or more constants, combined with zero or more operators. Two special tokens can be used in expressions: '$' and '$$'. They are the assembly position (program counter) and file position respectively, at the beginning of the current instruction. One could code: nop nop jr $-2 ; jump to the first nop All supported operators are listed below, starting with the lowest precedence-level. Operators with equal precedence are evaluated from left to right. Ofcourse any precedence can be overridden with the parenthesis '(' and ')'. 2.4.1 Precedence Level 0 - Relational Operators The relational operators are: x = y equals x <> y not equals x >< y " " x != y " " x < y less than x > y more than x <= y less than or equals x =< y " " " " x >= y more than or equals x => y " " " " Unlike in the C-language, the relational operators use -1 for 'true' and 0 for 'false' (in stead of 1 and 0). This way there's no need for boolean versions of the AND, OR and XOR operators, since they work in exactly the same way as the bitwise ones. The relational operators allow for complex expressions such as: x*(1+2*(x<0)) which gives the absolute value of 'x'. 2.4.2 Precedence Level 1 - Additive and (X)OR Operators These should speak for themselves. tniASM chooses to use 'OR' and 'XOR' keywords (as in BASIC) in stead of '|' and '^' characters (as in C). x + y Addition x - y Subtraction x OR y Bitwise OR x XOR y Bitwise XOR 2.4.3 Precedence Level 2 - Multiplicative and AND Operators x ^ y Exponentiation x * y Multiplication x / y Division x MOD y Modulo x << y Shift Left x >> y Shift Right (unsigned) x AND y Bitwise AND 2.4.4 Precedence Level 3 - Unary Operators + x unary plus - x unary minus NOT x one's complement 2.5 Pseudo Instructions 2.5.1 DB/DW Define a (string of) byte(s)/word(s). DB 255 DB "bla",13,10 DW 'AB',4000h DW "string may be odd" ; odd strings are 0-padded 2.5.2 DC Defines a string terminated by bit 7 being set. DC "TOKEN" ; same as DB "TOKE","N" OR 128 DC "LIST","STOP" ; defines 2 strings, both bit 7 terminated. 2.5.3 DS Define space (in bytes). DS 10 ; defines 10 bytes of 0 DS 10,255 ; defines 10 bytes of 255 DS 4000h-$ ; pad with 0 until address 4000h DS 0 ; doesn't do anything DS -1 ; the same goes for negative values 2.5.4 EQU Assign a value to a label. An EQU must follow a label on the same line. bankstart: EQU 4000h ORG bankstart ; same as org 4000h 2.5.5 FNAME Specify output file. Use FNAME to make tniASM output to a file other than 'tniasm.out'. You can use FNAME as much as you like throughout your source code in order to output different parts to different files. FNAME also sets FORG to 0. FNAME "output.bin" ; output file is now 'output.bin' {...} In stead of creating a new file, you can also instruct tniASM to output to an existing file by specifying a second parameter to FNAME. This second parameter is the file position where tniASM will output to, and FORG is automatically set with this value. FNAME "output.bin",1024 ; output starts at position 1024 in the ; existing file 'output.bin' FNAME "output.bin",0 ; output starts at position 0, like normal, ; but in an existing 'output.bin' 2.5.6 FORG Set output file position. You can use FORG to cause tniASM to output at a certain file position. If the position is larger than the file it will be padded with 0's. When no FORG is given, the starting file position will be 0. 2.5.7 INCBIN Include binary file. The INCBIN instruction includes a binary file in the current machine code output. It's particularly useful for embedding graphics or large tables that you wouldn't want to have in huge DB lists. music1: INCBIN "music1.bin" .end: INCBIN optionally takes 1 or 2 more parameters. The first is the offset in the file to be included. The second is the total length of data to be included. INCBIN "basic.bin",7 ; include from offset 7 INCBIN "cutout.bin",1024,512 ; include 512 bytes from ; offset 1024 2.5.8 INCLUDE The INCLUDE instruction includes another file in the current source file, in nesting levels as deep as memory permits. {...} INCLUDE "incthis.asm" {...} 2.5.9 ORG ORG allows one or two arguments. The first sets the assembly position (program counter) to an address, while the second argument gives the maximum allowable address for this 'section'. If the address is exceeded, tniASM will issue a warning. This warning ignores any PHASE'ing. When no ORG is given, the starting assembly position will be 0. ORG 0c000h ; following code starts as if from 0c000h ORG 0c000h,0 ; same as above ORG 4000h,7FFFh ; start from 4000h, warn if exceeding 7FFFh 2.5.10 PHASE/DEPHASE PHASE 'phases' the assembly position to the address specified. This is particularly useful for code that gets relocated later. DEPHASE phases back to the normal assembly position. A new PHASE or ORG command DEPHASE's any previous PHASE'ing. ; this example relocates the routine SetS#0 from its current ; address to 0C000h. Because of the PHASE/DEPHASE its label ; 'SetS#0' already points to 0C000h. ORG 8000h ld hl,start ld de,SetS#0 ld bc,SetS#0.end-SetS#0.start ldir {...} SetS#0.start: PHASE 0C000h SetS#0: xor a ; set V9938 S#0 out [99h],a ld a,15+128 out [99h],a ret DEPHASE .end: 2.5.11 RB/RW Reserve a (number of) byte(s)/word(s) as uninitialised data. This is basically the same as DS, but does not update the file position, neither does it output anything. It merely updates the assembly position. RB and RW are useful when declaring variables in RAM. ORG 0C000h Var1: RB 2 ; Var1 = 0C000h Var2: RW 1 ; Var2 = 0C002h Var3: RB 0 ; Var3 = 0C004h Var4: RW -1 ; Var4 = 0C004h because zero and ; negative values are ignored 2.6 Conditional Assembly Sometimes it's useful to have a certain piece of code assemble only when certain conditions are met. For instance when writing code for multiple platforms at the same time (Z80 and R800 for example), or for including/excluding debug code. tniASM provides this functionality through the IF-construct. Its basic form is: IF {operand} [{...}] [ELSE [{...}]] ENDIF Note that due to the multi-pass nature of tniASM, it's allowed to use forward references in IF-constructs. They may also be used accross source file boundaries. Ofcourse IF's can be nested with a practically infinite depth. 2.6.1 IF {expression} The expression is evaluated and is considered 'false' when zero, while any non-zero result is considered 'true'. loop: {...} IF $-loop < 128 djnz loop ELSE dec b jp nz,loop ENDIF 2.6.2 IFDEF {label} Check if a label was previously declared this pass. R800: ; comment away for Z80 version IFDEF R800 mulub a,b ELSE call mulub_a_b ENDIF IFDEF R800 ELSE mulub_a_b: {...} ret ENDIF 2.6.3 IFEXIST {string} Check if a file exists. Look at the second example for a nice trick, which works with any IF-instruction. IFEXIST "test" {...} ENDIF ; do {...} if "test" exists IFEXIST "test" ELSE {...} ENDIF ; do {...} if "test" does not exist 2.6.4 IFEXIST {label} Similar to IFDEF, but checks if a label exists regardless of where or when it is declared. You can use this to check if a label is declared further on in the source code. 2.7 Multi CPU support tniASM can assembly code for several CPU's, namely Z80, R800 and the processor commonly known as GBZ80. By default, tniASM assumes it is working in R800/MSX mode. Using the "CPU" instruction, one can switch between the following modes. This can be done anywhere in your code and as often as you wish. The modes are called "Z80", "R800" (plus the alias "MSX") and "GBZ80". CPU Z80 ; switch to Z80 mode CPU R800 ; switch to R800 mode CPU MSX ; equivalent to R800 mode CPU GBZ80 ; switch to GBZ80 mode 2.7.1 Z80 mode This mode does not accept the R800 MULUB/MULUW opcodes, but otherwise is the same as R800/MSX mode. Differences with standard Z80 syntax rules are: - [ and ] is supported for indirection, in addition to ( and ). So if you want to read a word from memory address 4000h, you can code LD HL,[4000h] as equivalent of LD HL,(4000h). - For ADD, ADC, SUB, SBC, AND, XOR, OR and CP, the accumulator is optional. So CP A,B and CP B are equivalent. - IN [C] or IN F,[C] can be used. (Z80 undocumented) - IX and IY can be split up in IXH, IXL, IYH, IYL respectively. (Z80 undocumented) - SLL (including alias SLI) is supported. (Z80 undocumented) - PUSH and POP take a register list, evaluated from left to right. PUSH AF,BC,DE,HL ; pushes AF, BC, DE and HL in that order. POP HL,BC ; pops HL and then BC. 2.7.2 R800 or MSX mode All Z80 and R800 opcodes are accepted. Differences with standard R800 syntax rules are: - Z80 opcode and register names. - tniASM Z80 rules. (Chapter 2.7.1) - Note that SLL has a different (undocumented) function on R800. 2.7.3 GBZ80 Only GBZ80 opcodes and extensions are accepted. Differences with standard GBZ80 syntax rules are: - LD A,(HLI) and LD A,(HLD) (and vice versa) are written LDI A,[HL] and LDD A,[HL]. - LD A,(n) and LD A,(C) (and vice versa) are written LDH A,[n] and LDH A,[C]. Furthermore they have aliases IN A,[x] and OUT [x],A. The LDH A,[n] and LDH [n],A can take values between 0-FF and FF00-FFFF hex. - ADD SP,d is written LD SP,SP+d. - LDHL SP,d is written LD HL,SP+d. - tniASM Z80 rules. (Chapter 2.7.1) Chapter 3: Other Stuff ---------------------- 3.1 Tips and Tricks The best tip I can give you when working with tniASM is to keep one file in which you include every other file. Sort of like a makefile without dependencies and object stuff. ; "example.asm" ; this is an example 'makefile' fname "example.com" org 100h include "frontend.asm" include "main.asm" include "backend.asm" fname "example.dat" org 0 incbin "stuff.dat" include "somesubs.asm" 3.2 History 2 November 2011, v0.45 Magnum mercy shot - Fixed: Generate error for LD L,IXL (or similar) instead of simply outputting LD IXL,IXL. - Now supports using expressions for the fixed numeric operands in IM, RST, BIT, RES and SET instructions. Previously generated an error or wrong result! - Officially the last version of the v0.x series. See Chapter 3.3 for information about tniASM v1.0. 2 March 2005, v0.44 - Fixed: $ during PHASE 22 January 2005, v0.43 - The operator != is now supported as an alias for <>. - Fixed: A bug concerning IF-ELSE sometimes caused errors. - Updates in the manual (Chapter 2.7.2, 3.3) 14 November 2004, v0.42 - Fixed: A nested IF in an IF block that resolved as false caused an infinite loop. - Fixed: Defining a local label without a parent label now returns an error message. 17 September 2004, v0.41 - tniASM now handles source files using extended ASCII characters. 4 October 2003, v0.4 Special #msxdev Edition - Second command parameter now specifies initial output filename. (Same as using fname directive.) - Now allows spaces surrounding registers in indirect accesses. - PUSH and POP allow a list of registers, evaluated from left to right. - DC pseudo instruction added, see Chapter 2.5.2 - Updated the manual to reflect the changes in v0.35 and v0.4. 2 October 2003, v0.35 - Due to popular demand, hacked up a version that allows () for indirection, [] and () are now identical. 24 December 2002, v0.3 - About 2.5 times faster assembly. - Fixed things: * File error on INCBIN displayed wrong filename. * GBZ80 'LDI/LDD A,[HL]' caused an error. * ORG without warning argument didn't reset warning. * Local labels could change scope between passes. * 'INCLUDE' and 'CPU' parsing is more robust. * Minor manual corrections. 16 September 2000, v0.2 MSX Fair Bussum Edition - Conditional assembly, see chapter 2.6 - Practically unlimited label length, see chapter 2.2.1 - Octal numeric constants, see chapter 2.3.1 - Multi CPU support (added GBZ80), see chapter 2.7 - Uninitialised data declarations, see chapter 2.5.11 14 August 2000, v0.1 Initial Release 3.3 tniASM v1.0 tniASM v1.0 is a completely rewritten version of tniASM. It is really a whole different program. Completely processor-agnostic, even assembly-agnostic, it is ideal for custom processors and even non-assembly work like file manipulation. Continuing tniASM tradition, great care has been taken in remaining very easy to use while providing powerful features. The most important feature, the base of everything, is the powerful macro processor. It provides: - multiple CPU support, even within the same source file - customized assembly, use the (pseudo-)instructions YOU like - ability to be compatible with other assemblers tniASM v1.0 comes in versions for 32-bit Windows and 64-bit Linux, and macro definitions for: - Z80, R800, GBZ80 and Z380 processors - code, data and reserved data sections - tniASM v0.45 compatibility tniASM v1.0 is in production use for all projects by, among others, The New Image and Infinite. You too can participate in the private beta test by making a donation of at least 15 Euro to bank account BIC:INGBNL2A, IBAN:NL42INGB0006268083, of Patriek Lesparre in Almere, The Netherlands. Don't forget to indicate your e-mail address! If you prefer to use Paypal, donate to paypal@tni.nl. Beta testers will receive the latest development versions and can give their input on the development of the program. Once considered fit for the public, tniASM v1.0 is expected to be sold for 25 Euro, but beta testers will receive it for free. 3.4 Disclaimer - All trademarks are property of their respective owners. - tniASM v0.45 is freeware and can be distributed freely as long as it is not modified, this file is included in the archive, and it is not part of a commercial product. - Use it at your own risk. The author is not responsible for any loss or damage resulting from the use or misuse of this software. ---------------------------------------------------------------------------