============================================================================== AS1600 Quick-and-Dirty Documentation ============================================================================== All of the other C and H files in the source directory (except as1600.c) come from the public-domain retargetable Frankenstein Assembler by Mark Zenier. The Yacc description for the CP-1600 was written based loosely on the existing as2650 description, and was written by yours truly, Joe Zbiciak. The Frankenstein Assembler is in the public domain. My modifications to the Frankenstein Assembler are hereby placed under GPL. (Eventually, I plan to clean up the 100s of warnings that GCC spews out for the code. Eventually.) Note: General documentation for Frankenstein is in the file 'as1600.ps'. The full, original source for the Frankenstein Assembler can be downloaded from http://spatula-city.org/~im14u2c/intv/franky.zip Have fun. Questions, problems: intvnut AT gmail.com (Joe Zbiciak) QuickStart: -- To invoke the assembler on the source file "program.asm", producing the listing file "program.lst" and the object binary "program.rom", issue the following command: as1600 -o program.rom -l program.lst program.asm Any errors will be recorded in the assembly listing file. The output is in Intellicart .ROM format. To generate a BIN+CFG format output instead, run: as1600 -o program.bin -l program.lst program.asm This will generate "program.bin" and "program.cfg" instead of "program.rom". Some additional quick notes not covered in the Postscript documentation: -- As of Release 004, AS1600 supports the following new directives and operators: MACRO, REPEAT/RPT, ERR, STRLEN, ASC and LISTING. See "Major New Directives And Operators" below for REPEAT, ERR, STRLEN and ASC. Macros are covered separately in the file "macros.txt". -- as1600 outputs either BIN+CFG or Intellicart ROM formats. It can handle arbitrary memory maps as a result. NOTE: Generating .ROM format directly gives you exact control over every memory range's memory attributes. The .CFG format does not give exact control, although it is good enough for most purposes. -- To generate a BIN and CFG file from a .ROM, use the "rom2bin" utility. The rom2bin utility will determine the memory map from the .ROM file and generate an appropriate BIN and CFG file from the assembler output. -- To generate a .ROM from a BIN and CFG file, use the "bin2rom" utility. The .ROM utility will parse the CFG and construct a .ROM file from your BIN. Note: The .CFG format cannot express the full flexibility of the .ROM format. If you're writing code which uses the Intellicart in advanced ways, you may run into issues with the .CFG format. That said, most sane programs have no problem with the .CFG format. -- I've added support for "include paths." The "-i pathname" adds a directory to the include-path. Also, the environment variable AS1600_PATH can specify additional directories to search. The search order for an INCLUDE is: -- Current directory -- Directories specified on command line -- Directories specified in AS1600_PATH environment variable For the AS1600_PATH environment variable, individual directory names should be separated by semicolons. For example: "/path/to/dir1;path/to/dir2;/path/to/dir3" The AS1600_PATH environment variable can be set from DOS using the SET command: SET AS1600_PATH="C:\path\to\dir1;C:\path\to\dir2" It can be set under UNIX and Linux by simple assignment. Under Bourne It can be set under UNIX and Linux by simple assignment. Under Bourne shell, the correct syntax is: AS1600_PATH="/path/to/dir1:/path/to/dir2" export AS1600_PATH Under C-Shell and tcsh, the correct syntax is: setenv AS1600_PATH "/path/to/dir1:/path/to/dir2" -- AS1600 Syntax differs sligthly from Carl's assembler. This assembler is much stricter about using GI's proper syntax. Also, procedure declarations are handled differently. Carl's: Frankenstein: ROMWIDTH EQU 10 ROMWIDTH 10 PROC FOO FOO: PROC XORI $123, R0 XORI #$123, R0 @@LBL: @@LBL: MVII #$123, R0 MVII $123, R0 ENDP ENDP JSR R5,FOO.LBL CALL FOO.LBL -- This assembler recognizes "SP" as an alias for R6, and "PC" as an alias for R7. -- This assembler supports a large number of pseudo-ops: TSTR Rx --> MOVR Rx, Rx CLRR Rx --> XORR Rx, Rx PSHR Rx --> MVO@ Rx, SP PULR Rx --> MVI@ SP, Rx JR Rx --> MOVR Rx, PC CALL addr --> JSR R5, addr BEGIN --> MVO@ R5, SP RETURN --> MVI@ SP, PC -- It also supports a number of CP-1600-specific directives: DCW --> Emit words to binary. Same as DECLE. DECLE --> Same as DCW, similar to BYTE. BIDECLE --> Outputs word in DBD format. Same as WORD. ROMWIDTH --> Sets ROM width (default is 16.) ROMW --> Alias for ROMWIDTH STRUCT/ENDS --> SEE BELOW MEMATTR --> SEE BELOW ORG --> SEE BELOW RMB/RESERVE --> SEE BELOW The BYTE, DCW, DECLE, BIDECLE and WORD directives have somewhat odd definitions. This is due to the fact that I've adapted an 8-bit assembler for a machine that is not byte-addressable. Further complicating things is the fact that the width of the output word is variable. Here is a short description of each: BYTE: Emits a single byte (in the range 0..255) to the output. The byte occupies exactly one location in the output. DECLE: Emits a single word to the output. The allowed range of values determined by the ROM width. For instance, if the ROM width is 10, then the allowed range is $000 - $3FF. If the ROM width is 16, then the allowed range is $0000 - $FFFF. The value will occupy exactly one location in the output. BIDECLE: Divides a 16-bit word into two 8-bit values. It outputs the lower 8 bits first, followed by the upper 8 bits. This is sometimes known as "DBD format". The value will occupy exactly two locations in the output. WORD: Identical to BIDECLE. -- This assembler will automatically insert SDBDs in many cases for immediate-mode instructions. See the description of ROMW (next bullet) for more details. -- The ROMW directive accepts one or two parameters. The first parameter is the ROM width. The second parameter changes the the assembler's behavior on immediate values. The default is Mode 0 -- assume forward references do not need SDBD. You can change this to Mode 1 -- assume forward references DO need SDBD. Ordinarily, the assembler automatically inserts SDBD instructions for immediate operands as is necessary. However, this does not work when an expression or symbol is not yet defined (eg. a forward reference). By default, the assembler assumes these references will fit within the ROM width, and it requires you to explicitly provide SDBD where they won't. In Mode 1, the assembler will insert an SDBD in this case, and issue a warning telling you it did so. Limitations: -- Error reporting isn't all that great when constant widths overflow the ROM width. All you get is "expression exceeds available field width", which isn't 100% descriptive. -- When using SDBD Mode 0 (see ROMW description above), you will still need to use SDBDs when making forward references which will not fit in the immediate field width. There are also certain pathelogical cases in *both* SDBD modes where you will need to insert an explicit SDBD. Honestly, just set ROMW 16 and don't sweat the details. :-) -- Code which works with Carl's assembler will not work with this one out-of-the-box, due to the difference in syntax on immediate-mode instructions. -- Multiple ORG statements or ROM images with gaps are supported, but one must still take care to stay within the desired memory map. There is no protection against spilling into areas of the memory map that you don't want to be in. See the "world" example in the "examples" directory for an example of a program that is larger than 8K words. ============================================================================== Major New Directives And Operators ============================================================================== I've made some additions to as1600 to support Intellicart bank switching, overlays, and so on. To do this, I've extended "ORG" and "RMB" (aka RES or RESERVE), and I've added a new directive "MEMATTR" below. ------------------------------------------------------------------------------ [label] ORG inty_addr [, icart_addr[, icart_attr]] ------------------------------------------------------------------------------ Sets the current program counter, and optionally a different load address within the Intellicart's address space. It can also specify the memory attributes for the given range of addresses in the Intellivision's address map. -- inty_addr is the address at which the code will appear in the Intellivision address map. -- icart_addr is the address at which the code will be loaded into the Intellicart address map. If unspecified, "icart_addr == inty_addr". -- icart_attr is a string defining the attributes for this range of memory. The icart_attr defaults to "+R" if "icart_addr == inty_addr", or "" if "icart_addr != inty_addr". See the description below for a definition of "icart_attr" strings. NOTE: The memory attributes are assigned on the corresponding range of addresses in the Intellicarts's address map, not the Intellivision's. If you are constructing overlays, you will need to set the attributes on the overlay window separately. EXAMPLE: Overlaid Tables This example assembles two pairs of lookup tables. They both pairs are intended to reside at $6000 in the Intellivision memory map, but one will be loaded at $0000 in the Intellicart address space and the other will be loaded at $1000. The tables can be selected using the Intellicart's bankswitching functionality. ; Set up TABLE1A/TABLE1B to load at $0000 in Intellicart. ; The symbols for TABLE1A and TABLE1B will show them residing ; at $6000 and $6010. ORG $6000, $0000, "-RWBN" TABLE1A PROC DECLE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ENDP TABLE1B PROC DECLE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ENDP ; Set up TABLE2A/TABLE2B to load at $1000 in Intellicart. ; The symbols for TABLE2A and TABLE2B will show them residing ; at $6000 and $6010. ORG $6000, $1000, "-RWBN" TABLE2A PROC DECLE 0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15 ENDP TABLE2B PROC DECLE 0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15 ENDP ; Finally, reserve some readable memory at $6000 that is ; bankswitched, so that we can switch in these tables on the ; fly: ORG $6000, $6000, "=RB" RMB 32 ; Each pair of tables is 32 words long ------------------------------------------------------------------------------ [label] RMB count [label] RES count [label] RESERVE count ------------------------------------------------------------------------------ Advances the program counter by 'count' words, without defining the contents of the storage. The range of addresses are assigned the currently active memory attributes as specified in the most recent "ORG" statement. The RMB (aka RES or RESERVE) directive is useful for allocating storage to global and local variables without having to manually assign addresses to objects. In conjunction with ORG, it's also useful for allocating bankswitched ranges as shown above. ------------------------------------------------------------------------------ MEMATTR icart_attr, addr_lo, addr_hi ------------------------------------------------------------------------------ Adjusts attributes on an inclusive range of addresses in the Intellivision's address map. This directive should be used carefully and sparingly. The most common use of MEMATTR is to mark a range of uninitialized memory as read/write and possibly bankswitched. MEMATTR is provided as an "out" for unforseen circumstances. Most programs shouldn't need it, and should use ORG and RMB together. ------------------------------------------------------------------------------ [label] STRUCT [addr] @@[local_label]: EQU $ + offset ENDS ------------------------------------------------------------------------------ The STRUCT directive is very similar to PROC. It is an enclosure that is intended to make it easy to define a set of related symbols. It supports local labels in a manner similar to PROC. STRUCTs cannot nest, and cannot be contained within PROCs. STRUCTs are ended by the ENDS directive. The primary difference of STRUCT as compared to PROC is that it accepts a starting address. Also, the main program counter is not advanced while a STRUCT is active. STRUCTs have their own private program counter. This makes them useful for defining the structure of objects in memory, such as peripherals. Consider this example from gimini.asm. It defines a set of labels named PSG0.register which map to the 16 registers in the Programmable Sound Generator. In this example, notice how '$' expands to the address of the STRUCT itself ($1F0). PSG0 STRUCT $01F0 @@chn_a_lo EQU $ + 0 ; Channel A period, lower 8 bits of 12 @@chn_b_lo EQU $ + 1 ; Channel B period, lower 8 bits of 12 @@chn_c_lo EQU $ + 2 ; Channel C period, lower 8 bits of 12 @@envlp_lo EQU $ + 3 ; Envelope period, lower 8 bits of 16 @@chn_a_hi EQU $ + 4 ; Channel A period, upper 4 bits of 12 @@chn_b_hi EQU $ + 5 ; Channel B period, upper 4 bits of 12 @@chn_c_hi EQU $ + 6 ; Channel C period, upper 4 bits of 12 @@envlp_hi EQU $ + 7 ; Envelope period, upper 8 bits of 16 @@chan_enable EQU $ + 8 ; Channel enables (bits 3-5 noise, 0-2 tone) @@noise EQU $ + 9 ; Noise period (5 bits) @@envelope EQU $ + 10 ; Envelope type/trigger (4 bits) @@chn_a_vol EQU $ + 11 ; Channel A volume / Envelope select (6 bits) @@chn_b_vol EQU $ + 12 ; Channel B volume / Envelope select (6 bits) @@chn_c_vol EQU $ + 13 ; Channel C volume / Envelope select (6 bits) @@io_port0 EQU $ + 14 ; I/O port 0 (8 bits) @@io_port1 EQU $ + 15 ; I/O port 1 (8 bits) ENDS STRUCT can also be useful for constructing a set of related constants under a single umbrella. Consider the following example, adapted from gimini.asm: PSG STRUCT $0000 ; Constants, etc. common to both PSGs. ;;----------------------------------------------------------;; ;; Bits to OR together for Channel Enable word ;; ;;----------------------------------------------------------;; @@tone_a_on EQU 00000000b @@tone_b_on EQU 00000000b @@tone_c_on EQU 00000000b @@noise_a_on EQU 00000000b @@noise_b_on EQU 00000000b @@noise_c_on EQU 00000000b @@tone_a_off EQU 00000001b @@tone_b_off EQU 00000010b @@tone_c_off EQU 00000100b @@noise_a_off EQU 00001000b @@noise_b_off EQU 00010000b @@noise_c_off EQU 00100000b ENDS Notice that this example does not make use of the STRUCT's address. Therefore, the address was set to 0. And finally a word of warning: While one may include code inside a STRUCT, this is strongly discouraged. The results of such an action are NOT well defined. ------------------------------------------------------------------------------ Syntax of icart_attr ------------------------------------------------------------------------------ The "icart_attr" argument is a case-insensitive string whose format is similar to the UNIX "chmod" command. The string may specify relative or absolute attributes for the particular range of addresses. Because attribute mode changes are processed in linear order at assembly time, the final value of the memory attributes for a given range of addresses is determined by the order in which attributes are applied. For this reason, avoid specifying overlapping attributes that conflict. Attribute strings take on the following forms: [{ACTION}{FLAGS}[,{ACTION}{FLAGS}]] ACTION is one these characters: + Set the following flag bits - Clear the following flag bits = Set the memory attributes to this exact pattern of flags FLAGS is a list of characters from the following set: R Readable W Writable N Narrow (8-bit memory -- not supported by Intellicart.) B Bank-switchable Notes: -- Up to two actions may be specified, but the "=" action only makes sense when used alone. -- Attribute strings are case insensitive, and the ordering within a set of flags is unimportant. -- Empty strings are valid, and represent "no change" on memory attributes. Examples: "+RWN" ; Add readable, writable and 8-bit flags to memory "+RB" ; Add readable and bank-switchable to memory "-W" ; Remove writable flag from memory. "=RW" ; Mark memory as exactly readable and writable. "+RB,-W" ; Add readable, bank-switchable, remove writable This syntax is probably overkill, and it certainly provides the programmer with enough rope to shoot himself in the foot. ;-) But that's what assembly's all about, right? ------------------------------------------------------------------------------ REPEAT [constant expression] ; block to repeat ENDR or: RPT [constant expression] ; block to repeat ENDR ------------------------------------------------------------------------------ The REPEAT directive causes the enclosed block to be repeated in the output file the specified number of times. The argument must be a constant expression -- that is, an expression whose exact value is known at the point the REPEAT block is first reached. The mnemonic "RPT" is a synonym for REPEAT. The repeat count must be >= 0. If the count == zero, the enclosed block is skipped -- that is, no code object code is generated for the enclosed block. If the count == 1, the block is copied and assembled as-is. If the count > 1, then the block is copied and assembled a total of 'count' times -- once within the REPEAT/ENDM block, and count-1 times immediately following ENDM. When the count is larger than 1, the assembler will insert comments to delimit the repeated copies. EXAMPLE #1: ; Copy 8 words REPEAT 8 MVI@ R4, R0 MVO@ R0, R5 ENDM This assembles to (from the listing file): ; Copy 8 words REPEAT 8 0000 02a0 MVI@ R4, R0 0001 0268 MVO@ R0, R5 ENDR ;== 1 0002 02a0 MVI@ R4, R0 0003 0268 MVO@ R0, R5 ;== 2 0004 02a0 MVI@ R4, R0 0005 0268 MVO@ R0, R5 ;== 3 0006 02a0 MVI@ R4, R0 0007 0268 MVO@ R0, R5 ;== 4 0008 02a0 MVI@ R4, R0 0009 0268 MVO@ R0, R5 ;== 5 000a 02a0 MVI@ R4, R0 000b 0268 MVO@ R0, R5 ;== 6 000c 02a0 MVI@ R4, R0 000d 0268 MVO@ R0, R5 ;== 7 000e 02a0 MVI@ R4, R0 000f 0268 MVO@ R0, R5 ;== 8 EXAMPLE #2: ; Just copy 1 word REPEAT 1 MVI@ R4, R0 MVO@ R0, R5 ENDR This assembles to (from the listing file): ; Just copy one word REPEAT 1 0000 02a0 MVI@ R4, R0 0001 0268 MVO@ R0, R5 ENDR EXAMPLE #3: ; No code will be generated REPEAT 0 MVI@ R4, R0 MVO@ R0, R5 ENDR This assembles to (from the listing file): ; No code will be generated REPEAT 0 MVI@ R4, R0 MVO@ R0, R5 ENDR Notice that although the lines from the source appear in the listing file, no code was generated (nothing appears to the left of the code). RESTRICTIONS: -- Repeat counts must be >= 0. -- Repeat blocks may not nest. -- Repeat blocks may not cross file boundaries. -- Repeat blocks may not contain INCLUDE directives. Other details: -- Repeat blocks are processed after macro expansion. Repeat blocks may therefore contains expanded macros. -- Errors reported for repeated lines contain the line number of the original line. In the case of a macro-expanded line, this line number will be the line of the pre-expansion source line. -- MACRO definitions contained within a repeat block will not be repeated. All other text within the repeat block *will* be repeated. ------------------------------------------------------------------------------ ERR "string" ------------------------------------------------------------------------------ This directive issues an assembler error with the indicated message string. This is intended to be used inside a conditional-assembly directive, in order to indicate that some condition was not met. Potential uses include error-checking for macro arguments, and checking to ensure assembly did not extend beyond certain ROM boundaries. Example: IF ($ > $7000) ERR "Program code extends beyond location $7000." ENDI ------------------------------------------------------------------------------ LISTING "on" LISTING "off" LISTING "code" LISTING "prev" ------------------------------------------------------------------------------ Controls the listing file generation mode. The default is "on", where all source code lines are echoed to the listing file. The mode "off" causes no lines to be echoed to the listing file. The mode "code" causes only those lines which generate object code to be echoed to the listing file. Whenever a LISTING directive is encountered, the assembler pushes the previous mode onto a "listing mode stack". To return to the previous listing mode, use the listing mode "prev". The listing mode stack is 255 modes deep. Overflowing this stack is not an error. The dropped entries are replaced with the mode "on". The main purpose of the "code" listing mode is within macros that have extensive conditional-assembly directives. By including a LISTING "code" directive at the beginning and a LISTING "prev" directive at the end, one can produce rather clean macro assemblies. Consider, for example, this beast: MACRO gfx_row s LISTING "code" ; start of graphics definition _gfx_x SET 0 _gfx_b SET $8000 _gfx_w SET (_gfx_w SHR 8) REPEAT 8 _gfx_w SET (_gfx_w + _gfx_b*((ASC(%s%,_gfx_x)<>$20) AND (ASC(%s%,_gfx_x)<>$2E) AND (ASC(%s%,_gfx_x)<>0))) _gfx_x SET _gfx_x + 1 _gfx_b SET _gfx_b SHR 1 ENDR _gfx_eo SET _gfx_eo + 1 IF _gfx_eo = 2 DECLE _gfx_w _gfx_eo SET 0 ENDI LISTING "prev" ENDM Now consider the following code which invokes this macro: _gfx_eo SET 0 _gfx_w SET 0 gfx_row ".######." gfx_row "#......#" gfx_row "#.#..#.#" gfx_row "#......#" gfx_row "#.#..#.#" gfx_row "#..##..#" gfx_row "#......#" gfx_row ".######." The following listing output is generated, thanks to the LISTING "code" and LISTING "prev" directives: ; gfx_row ".######." ; gfx_row "#......#" 0000 817e DECLE _gfx_w ; gfx_row "#.#..#.#" ; gfx_row "#......#" 0001 81a5 DECLE _gfx_w ; gfx_row "#.#..#.#" ; gfx_row "#..##..#" 0002 99a5 DECLE _gfx_w ; gfx_row "#......#" ; gfx_row ".######." 0003 7e81 DECLE _gfx_w ------------------------------------------------------------------------------ STRLEN ("string") ------------------------------------------------------------------------------ This operator returns the length of the enclosed string. This length is a constant expression and can be used in SET/EQU directives, or as the argument to a REPEAT block. This operator is mostly useful in a macro context. ------------------------------------------------------------------------------ ASC ("string", index) ------------------------------------------------------------------------------ This operator returns the ASCII value of the character at given index within the string. ASC returns 0 for indices that are beyond the end of the string. The index must be a constant expresion. ASC returns a constant expression, and so can be used in SET/EQU directives, or as the argument of a REPEAT block. This operator is mostly useful in a macro context. ------------------------------------------------------------------------------ label QSET expr => "Quiet" SET label QEQU expr => "Quiet" EQUate ------------------------------------------------------------------------------ These directives behave identically to SET/EQU, except that they mark the symbol as "quiet." A "quiet" symbol functions like an ordinary symbol, but will not appear in AS1600's symbol table output. This is useful inside macros to keep macro-internal symbols from cluttering up the symbol table and listing output files. ------------------------------------------------------------------------------ WMSG "string" => Warning message CMSG "string" => Comment message SMSG "string" => Status message ------------------------------------------------------------------------------ These three related directives write out messages of various forms: -- WMSG writes out an assembler warning from "string" -- CMSG writes out a comment message in the listing from "string" -- SMSG writes out a status message in the listing and to stdout from "string" ------------------------------------------------------------------------------ Stringification operators: $( expr-list ) => Stringify expr-list $#( expr ) => Render expr as signed decimal string $$( expr ) => Render expr as a 4 character hex string $%( expr ) => Render expr as an 8 character hex string ------------------------------------------------------------------------------ These operators produce strings that are usable in most contexts that require or accept strings. The only exceptions are INCLUDE, ORG and LISTING directives. The generic stringify operator $( ) produces a string from the lower byte of each value in expr-list. If one of the expressions in the list is undefined or not computable, stringify will substitute in a question mark ('?'). The numeric stringify operators $#( ), $$( ), and $%( ) return a string rendered either as a signed decimal value, 4 character hex value or 8 character hex string. If the value is undefined or not computable, the stringify operator will return an appropriate number of question marks. NOTE: The generic stringify operator $( ) assumes no character translation is currently active, and will warn if it detects that a translation table is currently active. It warns due to the potential for a subtle interaction between these two features. Strings decay to expression lists as needed. AS1600 applies character translations when converting strings to expression lists. The stringify operator takes expression lists and converts them back into strings. That makes complicated expressions such as this work: SMSG $(" Game size: $", $$(_SIZE), " (", $#(_SIZE), " decimal) words") However, when a character translation table is active, the string gets converted when decaying to an expression list. When $( ) then converts the list back into a string, there is no way to do a reverse mapping.