============================================================================== 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: -- The string %% in the body of a macro expands to an integer representing the total number of macro expansions. This makes it easy to define macro-local labels. -- 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:ecs_page [, icart_attr] ------------------------------------------------------------------------------ Sets the current program counter, and specifies the address to be page- flipped using ECS-style page flipping. icart_attr defaults to "=R" if not specified. (Remaining description TBD. Read below for definition of icart_attr.) ------------------------------------------------------------------------------ [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 addr_lo, addr_hi, icart_attr ------------------------------------------------------------------------------ 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/ENDR 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 cross file boundaries. -- Repeat blocks may not contain INCLUDE directives. -- Repeat blocks may nest; however, older versions of AS1600 break with certain nesting patterns, and the current version may still have issues. Other details: -- Repeat blocks are processed after macro expansion. Repeat blocks may therefore contain expanded macros. -- Although REPEAT 0 disables a block, the macro expander still expands macros within a REPEAT 0 block. Therefore, you cannot use REPEAT 0 to terminate macro recursion. -- 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. -- If you use IF/ELSE/ENDI to control expressions within a REPEAT block, and those expressions contain macros, you may need to use _EXPMAC to force macro expansion. See next section. ------------------------------------------------------------------------------ IF _EXPMAC expr Force macro expansion inside IF/ENDI ------------------------------------------------------------------------------ The macro engine allows recursive macros, using IF/ENDI to terminate the recursion. For example: ;; Generate a hailstone sequence: MACRO Collatz(c) .c0 QSET %c% .c QSET .c0 DECLE .c IF .c0 > 1 IF (.c AND 1) = 0 .c QSET .c / 2 ELSE .c QSET 3 * .c + 1 ENDI Collatz(.c) ENDI ENDM However, this interferes with the REPEAT mechanism. The macro expander explicitly ignores lines in the not-taken branch of an IF. The REPEAT block resides completely after the macro engine. It caches the output of the macro expander to replay it. If the taken vs. not-taken legs of the IF vary across a REPEAT block's iterations, then the resulting code will not work. Example: MACRO EmitEven(x) DECLE (%x%) * 8 + C_WHT ENDM MACRO EmitOdd(x) DECLE (%x%) * 8 + C_RED ENDM v QSET 0 REPEAT 6 IF v AND 1 EmitOdd(v) ELSE EmitEven(v) ENDI v QSET v + 1 ENDR The above code fails to assemble, because the macro expander only expands EmitEven(), and not EmitOdd(). The new _EXPMAC keyword fixes this, by forcing macro expansion inside the entire body of the IF: MACRO EmitEven(x) DECLE (%x%) * 8 + C_WHT ENDM MACRO EmitOdd(x) DECLE (%x%) * 8 + C_RED ENDM v QSET 0 REPEAT 6 IF _EXPMAC v AND 1 ; <== Added keyword EmitOdd(v) ELSE EmitEven(v) ENDI v QSET v + 1 ENDR This only works with non-recursive macros. Recursive macros require an IF to terminate expansion. That happens before the repeat buffer can see the expansion. Therefore, REPEAT blocks and recursive macros are incompatible. The _EXPMAC feature was added in January 2018. You can test for its presence by testing whether __FEATURE.EXPMAC is defined. ------------------------------------------------------------------------------ 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 ------------------------------------------------------------------------------ LISTCOL expr1, expr2, expr3 ------------------------------------------------------------------------------ (Available if __FEATURE.LISTCOL defined.) This directive controls the formatting of the listing file. Specifically: expr1: Number of hex values shown on lines with source code expr2: Number of hex values shown on lines without source code expr3: Starting column for source code The defaults are equivalent to LISTCOL 4, 8, 32. The value for expr3 must be greater than or equal to 7 + 5*expr1. All three expressions must be greater than 0. expr1 and expr2 must be less than 256, and expr3 must be less than 2048. Example: ORG $7000 LISTCOL 4, 8, 32 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 LISTCOL 4, 4, 32 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 LISTCOL 8, 8, 52 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 Results in: 0x7000 ORG $7000 LISTCOL 4, 8, 32 7000 0000 0001 0002 0003 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 7004 0004 0005 0006 0007 0008 0009 0000 0001 700C 0002 0003 0004 0005 0006 0007 0008 0009 7014 0000 0001 0002 0003 0004 0005 0006 0007 701C 0008 0009 0000 0001 LISTCOL 4, 4, 32 7020 0000 0001 0002 0003 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 7024 0004 0005 0006 0007 7028 0008 0009 0000 0001 702C 0002 0003 0004 0005 7030 0006 0007 0008 0009 7034 0000 0001 0002 0003 7038 0004 0005 0006 0007 703C 0008 0009 0000 0001 LISTCOL 8, 8, 52 7040 0000 0001 0002 0003 0004 0005 0006 0007 DECLE 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1 7048 0008 0009 0000 0001 0002 0003 0004 0005 7050 0006 0007 0008 0009 0000 0001 0002 0003 7058 0004 0005 0006 0007 0008 0009 0000 0001 ERROR SUMMARY - ERRORS DETECTED 0 - WARNINGS 0 ------------------------------------------------------------------------------ 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" User messages are always printed regardless of the listing mode. ------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------ Expresison-list operators: expr-list, expr => concatenates to an expr-list ( expr-list )[ expr ] => returns an element from an expr-list ( expr-list )[ expr, expr ] => Slice expr-list, returns new expr-list ------------------------------------------------------------------------------ Expression lists are built up by concatenating expressions together with commas. You can index a specific value in an expression list using the second syntax. Indexing past the end of an expression list returns 0. Because strings decay to expression lists, ("string")[index] is a synonym for ASC("string", index). This only works for strings. If you have a general expression list, you must use (expr-list)[index]. The expr-list slice operator (expr-list)[expr,expr] returns a new expr-list which is a subset of the elements from the original expr-list. The slice indices are [first,last], inclusive. If last < first, then the values in the slice will be in reverse order as compared to the original values. See exprlist_examples.asm for some examples. ------------------------------------------------------------------------------ CLASSIFY( thing ) => Returns an integer saying what 'thing' is. ------------------------------------------------------------------------------ This can be useful in macros that need to behave polymorphically. CLASSIFY will return one of the following values: CLASS.ABS EQU (-1) ; Absolute expression (e.g. value is known) CLASS.SET EQU (-2) ; Symbol defined by 'SET' CLASS.EQU EQU (-3) ; Symbol defined by 'EQU' CLASS.STRING EQU (-4) ; "A quoted string." CLASS.FEATURE EQU (-5) ; Keyword corresponding to a FEATURE CLASS.RESV EQU (-6) ; Reserved word (AND, OR, SHL, etc.) CLASS.EMPTY EQU (-7) ; An empty argument, e.g. CLASSIFY() CLASS.UNUSED EQU (-8) ; Should never happen (unused slot in symtab) CLASS.UNKNOWN EQU (-9) ; Unable to classify argument CLASS.UNDEF EQU (-10000) ; Expression with undefined value Or, if the argument corresponds to one of the CPU registers R0 through R7, it will return a value in the range 0..7. R0 => 0, R1 => 1, etc. Note that SP and PC are recognized as synonyms for R6 and R7, and will return 6 and 7 respectively. Nomenclature: Absolute expressions are expressions whose value is already known. SET and EQU symbols are also absolute expressions, so if you want to key off of "I know the value of this expression", then you probably want to check for all three values (ABS, SET, EQU). Undefined expressions are expressions that incorporate a symbol whose value is not known yet. These are generally forward references to labels. Reserved words are generally the tokens the assembler recognizes as operators, such as AND, OR, SHL, etc. They do _not_ include mnemonics, as amazingly enough, mnemonics can be used as labels. This is a legal line of code: MVI: MVI MVI, R0 So, CLASSIFY(MVI) checks the /label/ MVI, not whether MVI is a mnemonic. See "classify_example.asm" in this directory for more extensive examples. ------------------------------------------------------------------------------ CFGVAR "var" = value => Declares a config variable w/ a numeric value CFGVAR "var" = "string" => Declares a config variable w/ a string value ------------------------------------------------------------------------------ The CFG file output as part of a BIN+CFG build may include arbitrary variables in a section named [vars]. (Also, in recent jzIntv, .ROM supports a subset. See note at the end of this section.) The following is an example: [vars] jlp = 3 name = "Cartridge Title" Variables can have either numeric or string values. The CFGVAR directive allows you to add variables of your own to the CFG or ROM file. For example, to generate the example above, you might write: CFGVAR "jlp" = 3 CFGVAR "name" = "Cartridge Title" You can also use array symbols (see "Array Symbols" below) with CFGVAR to provide either the variable name, or the string value. The syntax is slightly odd, due to the way array symbols function. N QSET "name" V QSET "Cartridge Title" ; Equiv to CFGVAR "name" = "Cartridge Title" CFGVAR $(N[0,N]) = $(V[0,V]) (Note: Array symbol support for CFGVAR was added 23-Dec-2017 and does not work with older versions of the assembler.) The following CFG variables have special meaning to jzIntv and other related tooling. Names are case sensitive: name The full name of this program. short_name Abbrev name of this program. 18 chars or less is best. * author Name of the program's author. * game_art_by Name of the artist who provided in-game artwork * music_by Name of in-game music composers, arrangers * sfx_by Name of the sound effects artist * voices_by Name of the voice actors * docs_by Name of the documentation author * box_art_by Name of the artist that did box, manual, overlay artwork * concept_by Creator of the game concept * more_info_at Free form string (URL preferred) for more information * publisher Name of the program's publisher. * license License this code is released under * release_date Date program was released (see below for date formats) * year Synonym for release_date * build_date Date program was built * version Free-form version string * description Free-form description string * desc Synonym for description ecs_compat Compat value: Compatibility level with ECS voice_compat Compat value: Compatibility level with Intellivoice intv2_compat Compat value: Compatibility with Intellivision 2 kc_compat Compat value: Compatibility with Keyboard Component tv_compat Compat value: Compatibility with TutorVision/INTV88 ecs 0 = same as ecs_compat = 1; 1 = same as ecs_compat = 3 voice 0 = same as voice_compat = 1; 1 = same as voice_compat = 2 intv2 0 = same as intv2_compat = 0; 1 = same as intv2_compat = 1 lto_mapper 0 = disable LTO mapper; 1 = enable. jlp_accel Enable JLP accelerator (see table below) jlp Synonym for jlp_accel. jlp_flash Specify JLP flash storage copacity in 1.5K byte blocks. Lines marked with a "*" can be repeated. For example, if a game has multiple authors, you can list them all with their own author variable. Likewise, if a program was released multiple times, you can give a list of release dates. For other values, repeating a variable does not have a well defined meaning. Typically (but not always), the first instance takes precedence. Dates are variable-precision quantities. They can specify as little as just the year, or they can specify all the way down to seconds, including timezone. The full date format is one of the following four patterns: YYYY-MM-DD HH:MM:SS +hhmm YYYY-MM-DD HH:MM:SS +hh:mm YYYY/MM/DD HH:MM:SS +hhmm YYYY/MM/DD HH:MM:SS +hh:mm That is: -- Year: 4 digits, 1900 - 2155 -- Month: 2 digits, 01 - 12 -- Day: 2 digits, 01 - 31 -- Hour: 2 digits, 00 - 23 -- Minutes: 2 digits, 00 - 59 -- Seconds: 2 digits, 00 - 60 (leap second permitted) -- + or - to indicate east/west of UTC -- Hours ahead/behind UTC (0 - 12) -- Minutes ahead/behind UTC Either slashes or dashes are permitted between the YYYY-MM-DD; however, you must be consistent. You can specify lower precision dates by leaving off later fields. For example: YYYY Year only YYYY-MM Year and month only YYYY-MM-DD HH:MM Year, month, day, hours, and minute For dates, years below 100 are interpreted as 19xx. So 80 means 1980, while 17 means 1917. Be mindful of Y2K. The valid range of years is 1901 to 2155, with 1 through 99 serving as aliased for 1901 to 1999. A year of 0 or any other out-of-range value is treated as an invalid/missing date. Compat values (for XX_compat vars) take on one of four integer values: 0 Incompatible with. 1 Don't care / tolerates. 2 Supports / is enhanced by. 3 Requires. "Incompatible with" means that the program will not run, or runs incorrectly when the given peripheral is in the system or when attached to the specified type of system. For example, certain 3rd party games don't run on INTV 2. "Don't care / tolerates" is the default state for most things. For example, Astrosmash does not care if you plug in the Intellivoice. It ignores it completely. "Supports / is enhanced by" means that the program supports the peripheral directly, and is enhanced in some way when it is present. For example, World Cup Soccer enables a 4-player mode when it detects ECS. Space Patrol adds 6-voice sound when it detects ECS. "Requires" means that the program will not run, or will run incorrectly if the peripheral is missing or if run on a different type of system. For example, Mind Strike will not play at all unless an ECS is attached: You need to press a key on the ECS keyboard to start the game. Now for an example: suppose *your* program has enhanced sound capabilities when the ECS is attached. You could mark it "ecs_compat" = 2. If your game crashes when the ECS is attached, you should mark it "ecs_compat" = 0. The jlp_accel value (aka. jlp) takes on one of 4 values: 0 JLP disabled 1 JLP accelerators enabled at reset, no JLP flash 2 JLP accelerators disabled at reset, JLP flash storage present 3 JLP accelerators enabled at reset, JLP flash storage present For jlp_accel >= 2, the jlp_flash value should be non-zero. If missing, it will take on a minimum value of 4. If set to 0, jlp_flash support will be disabled. For program license, consider using a short string to indicate a commonly known, existing license. Also note that "Public Domain" isn't really a license, so you might consider "CC CC0". I suggest the following strings: String License ------------ ------------------------------------------------------------ GPLv2 GNU General Public License, Version 2 GPLv2+ GNU General Public License, Version 2 or later GPLv3 GNU General Public License, Version 3 GPLv3+ GNU General Public License, Version 3 or later BSD3 BSD 3 Clause CC CC0 Creative Commons, no restrictions CC BY Creative Commons, attribution alone CC BY-SA Creative Commons, attribution, share alike CC BY-NC Creative Commons, attribution, non-commercial use CC BY-ND Creative Commons, attribution, no derivatives CC BY-NC-SA Creative Commons, attribution, non-commercial, share alike CC BY-ND-SA Creative Commons, attribution, no derivatives, share alike The following variables have default values if left unspecified: Variable Default Notes -------------- ------- -------------------------------------------------- ecs_compat 1 jzIntv's internal CRC database may override to 3. voice_compat 1 jzIntv's internal CRC database may override to 3. intv2_compat 1 kc_compat 1 tv_compat 1 lto_mapper 0 jlp_accel 0 jlp_flash 0 Or 4 if jlp_accel >= 2. jzIntv's built-in CRC database focuses only on well-known ROM images, and is intended to improve behavior when presented with a well-known game. -------------------------------- >>> NOTE ON THE .ROM FORMAT: <<< -------------------------------- Recent versions of jzIntv (Nov 2017) add metadata tag support for the .ROM format. These tags correspond to the variables described above. These get encoded as special binary tags appended to the ROM file. The assembler and bin2rom both know how to map CFGVARs to .ROM metadata tags, and will do so automatically. Conversely, rom2bin knows how to decode metadata tags into a [vars] section in a .CFG. The file "jzintv/doc/rom_fmt/id_tag.txt" discusses how the .ROM format supports these metadata tags. ------------------------------------------------------------------------------ val _ROTL16 amt Rotate 16-bit 'val' left by 'amt' val _ROTL32 amt Rotate 32-bit 'val' left by 'amt' val _ROTR16 amt Rotate 16-bit 'val' right by 'amt' val _ROTR32 amt Rotate 32-bit 'val' right by 'amt' ------------------------------------------------------------------------------ (Available if __FEATURE.ROTATE defined.) The _ROTxxx operators rotate the value on the left by the amount specified on the right. _ROTLxx rotates to the left, while _ROTRxx rotates to the right. The _ROTx16 operators truncate the value to 16 bits, and perform rotation on the lower 16 bits. The _ROTx32 operators operate on precisely 32 bits. ------------------------------------------------------------------------------ SRCFILE string, line Sets source file and line number in debug info ------------------------------------------------------------------------------ (Available if __FEATURE.SRCFILE defined.) This directive sets the current source filename to "string", and the current line number to "line". This overrides the source file and line number that would be written to the source map file. The goal here is to support high-level languages such as IntyBASIC. Provide a zero-length string or negative line number to disable the source file override. Example: ;FILE ./border.bas ;[1] CONST CS_WHITE = 7 SRCFILE "./border.bas",1 ;[2] Mode 0,13,6,13,6 SRCFILE "./border.bas",2 MVII #54893,R0 MVO R0,_color MVII #2,R0 MVO R0,_mode_select ;... many lines snipped ... SRCFILE "./border.bas",11 ; HERE Q3: B Q3 ;ENDFILE SRCFILE "",0 jzIntv uses the source-map file to provide source level debugging. AS1600's "-j foo.smap" flag (or "--src-map=foo.smap") generates the source-map file. jzIntv has a corresponding --src-map=foo.smap flag for reading the source map into the debugger. ------------------------------------------------------------------------------ TODAY_STR_LOC( "spec" ) => Returns string w/date info (localtime) TODAY_STR_GMT( "spec" ) => Returns string w/date info (GMT) TODAY_VAL_LOC( "spec" ) => Returns expr-list w/date info (localtime) TODAY_VAL_GMT( "spec" ) => Returns expr-list w/date info (GMT) ------------------------------------------------------------------------------ Note: You can test for the presence of the TODAY feature by checking whether __FEATURE.TODAY is defined. The spec string consists of the following format chars, each preceded by %. Where possible, these match the corresponding format chars in strftime(). (However, for portability reasons, AS1600 does not call strftime().) For TODAY_STR_xxx, whitespace and punctuation are copied through as-is to the output. For TODAY_VAL_xxx, whitespace and punctuation are ignored. Unrecognized format chars generate errors. TODAY_STR_xxx does not NUL terminate its strings. TODAY_xxx_LOC returns local time. TODAY_xxx_GMT returns GMT. Char Action ---- ----------------------------------------------------------------- %Y Current year. Always 4 digits as string. %y Current year, modulo 100. Always 2 digits as string. %m Current month (01 - 12). %d Current day of month (01 - 31). %H Current hour, 24hr clock (00 - 23). %M Current minute (00 - 59). %S Current seconds (00 - 61). %I Current hour, 12hr clock (01 - 12) %p AM/PM ("AM"/"PM" if string, 0/1 if value) %z Current timezone ("+HHMM" if string, minute delta to UTC if value) %% Output a % to the output For numeric quantities rendered as strings, all values are padded with zeros to keep the field widths fixed. Examples: STRING TODAY_STR_LOC( "%Y-%m-%d %H:%M:%S" ), 0 DECLE TODAY_VAL_GMT( "%Y%m%d%H%M%S" ) Generates: STRING "2017-12-25 18:09:49", 0 DECLE 2017,12,25,2,09,49 Because TODAY_VAL_xxx returns an expr-list, you need to be mindful when using TODAY_VAL_xxx with SET or EQU. year EQU TODAY_VAL_LOC( "%Y" ) DECLE year, year[0] The symbol 'year' gets declared as an array symbol. (See "Array Symbols" below.) The above example outputs something like: DECLE 0, 2017 Alternately, you can use expression list indexing to get the head element: year EQU (TODAY_VAL_LOC( "%Y" ))[0] DECLE year ------------------------------------------------------------------------------ ERR_IF_OVERWRITTEN expr Mark code as "not intended to be overwritten" FORCE_OVERWRITE expr Force code to be overwritten anyway ------------------------------------------------------------------------------ By default, AS1600 lets you assemble new code over addresses you've already assembled code into. That allows for some interesting tricks; however, most often this is really an error. The ERR_IF_OVERWRITTEN directive controls a flag that indicates whether the code that follows may be safely overwritten. 0 means "safe to overwrite", while 1 means "throw an error if overwritten." >>> Note: ERR_IF_OVERWRITTEN defaults to 0. You can change the default at >> the command line by adding the flag -e or --err-if-overwritten For example, if I wanted to fill some ROM with a fixed pattern, and then overwrite it with final code, I could do something like this: ERR_IF_OVERWRITTEN 0 ; About to write some filler data ORG $6000 REPEAT 4096 / 8 DECLE -1, -1, -1, -1, -1, -1, -1, -1 ENDR ERR_IF_OVERWRITTEN 1 ; Now overwrite it with real code ORG $6000 ; The following generates no errors or warnings. fun: PROC MVII #ISR, R0 MVO R0, $100 SWAP R0 MVO R0, $101 ;... ENDP ; This code, however, will trigger an error, because it's overwriting ; the code we just assembled at 'fun': ORG $6000 DECLE 12, 34 ; ERROR - ROM overwrite error on $6000 - $6001 The FORCE_OVERWRITE directive gives you the ability to forcibly overwrite code that was previously assembled with ERR_IF_OVERWRITTEN == 1. Revisiting the previous example: ERR_IF_OVERWRITTEN 1 ; Now overwrite it with real code ORG $6000 fun: PROC MVII #ISR, R0 MVO R0, $100 SWAP R0 MVO R0, $101 ;... ENDP ; With FORCE_OVERWRITE, this code now assembles without errors. FORCE_OVERWRITE 1 ORG $6000 DECLE 12, 34 The FORCE_OVERWRITE directive is meant for use in specialized macros that may wish to "back-patch" code that otherwise should have ERR_IF_OVERWRITTEN turned on. Use it sparingly. There is no way to query the current state of ERR_IF_OVERWRITTEN or FORCE_OVERWRITE. If you need to track that for some reason, wrap these in macros. Truth table: ERR_IF_OVERWRITTEN FORCE_OVERWRITE Result on an overwrite off off No error off ON No error ON off Report an error ON ON No error Note that ERR_IF_OVERWRITTEN tags current code to detect _future_ attempts to overwrite, while FORCE_OVERWRITE affects the code you're assembling right now. For example, this still generates an error, because the first DECLE was assembled with ERR_IF_OVERWRITTEN == 1: ERR_IF_OVERWRITTEN 1 ORG $6000 DECLE 1234 ERR_IF_OVERWRITTEN 0 ORG $6000 DECLE 3456 Conversely, this example does _not_ generate an error: ERR_IF_OVERWRITTEN 0 ORG $6000 DECLE 1234 ERR_IF_OVERWRITTEN 1 ORG $6000 DECLE 3456 ============================================================================== Array Symbols ============================================================================== As of approx 2009, AS1600 now has support for array symbols. Array symbols are a powerful extension on normal symbols. You can see some odditional examples in exprlist_example.asm in this directory. ------------------------------------------------------------------------------ Basic 1-D Array Symbols ------------------------------------------------------------------------------ You can create an array symbol with SET, QSET, EQU, or QEQU by assigning an expression _list_ to the value, rather than a single expression. Expression lists can even be strings: foo QSET 0, 1, 2 bar QSET "hello" Alternately, you can assign elements of an array symbol directly. If you assign to an index past the end, the array is extended; however, any missing elements remain undefined. baz[3] QSET 3 ; Must define baz[0], baz[1], and baz[2] before ref'ing Array symbols give symbols a dual identity: -- In most expression contexts, the symbol name returns the index of the last element in the array. For example: foo QSET 10, 20, 30 DECLE foo ; outputs 2 In reality, it returns either: -- The last assigned value to the scalar symbol, or -- The maximum of (last index assigned to) and (last scalar value of the symbol). This mostly gives you variable-sized array semantics if you play your cards correctly. -- If indexed by an absolute index, it returns the corresponding element: foo QSET 10, 20, 30 DECLE foo[1] ; outputs 20 -- If indexed by a pair of values, it returns an array slice as an expression list: foo QSET 10, 20, 30 DECLE foo[0,2] ; outputs 10, 20, 30 -- Reverse order slices are possible: foo QSET 10, 20, 30 DECLE foo[2,0] ; outputs 30, 20, 10 -- You can assign slices to array symbols, including yourself: foo QSET 10, 20, 30 foo QSET foo[2,0] DECLE foo[0,2] ; outputs 30, 20, 10 The canonical way to expand an entire array (assuming it's densely populated) is to ask for a slice from 0 to its own name. Since the array's name in an expression context returns its highest index, this works out well: foo QSET 10, 20, 30 DECLE foo[0, foo] ; outputs 10, 20, 30 This makes it easy to build up variable sized structures. For example, if you want to push a value onto the back of an array symbol, you can do something like this: stk[stk + 1] QSET val You can effectively change the length of an array symbol by assigning it with a scalar assignment. This works if you stick to the canonical way of referring to the entire array as a[0, a]. Recall that the scalar value of an array symbol represents its notional length; however, whether the array elements are defined or not actually exists separately of this. Consider this example: fun QSET 0,1,2,3,4,5,6,7,8,9 DECLE fun ; Outputs 9 DECLE fun[0,fun] ; Outputs 0,1,2,3,4,5,6,7,8,9 DECLE fun[0,9] ; Outputs 0,1,2,3,4,5,6,7,8,9 fun QSET 5 ; Notionally chops off the last 4 elements DECLE fun ; Outputs 5 DECLE fun[0,fun] ; Outputs 0,1,2,3,4,5 DECLE fun[0,9] ; Outputs 0,1,2,3,4,5,6,7,8,9 The assembler does not provide a way to unset a symbol. The length-tracking semantic, however, gives you a mechanism to say which elements are still valid. ------------------------------------------------------------------------------ Slice assignment ------------------------------------------------------------------------------ You can assign to a slice of an array, even from itself. For example: fun QSET 0,1,2,3,4,5,6,7,8,9 fun[4,7] QSET fun[7,4] ; Reverse elements 4 through 7. DECLE fun[0,9] This emits: 0, 1, 2, 3, 7, 6, 5, 4, 8, 9 ------------------------------------------------------------------------------ Multi-dimensional Symbols ------------------------------------------------------------------------------ The assembler supports multi-dimensional array symbols, to a limited extent: -- A multi-dimensional array symbol is inherently "ragged": Each row could be a different length. No attempt is made to keep a multi-dimensional array rectangular. -- Multi-dimensional arrays only support slices on their last dimension. ------------------------------------------------------------------------------ Stringifying arrays ------------------------------------------------------------------------------ You can stringify an array by using the expression-list-to-string operator $(). You need to reference an array slice to get the array contents. Without that, you'll end up with just the array length. For example: Warning QEQU "This is an example warning." WMSG $(Warning[0,Warning]) This will assemble like so, as expected: /tmp/wmsg.asm:3: WARNING - This is an example warning. ERROR SUMMARY - ERRORS DETECTED 0 - WARNINGS 1 ============================================================================== __FEATURE Summarizing available features ============================================================================== AS1600 defines a number of "feature symbols" to indicate the presence of support for a particular feature. The feature symbols take a single common form: __FEATURE.{name} where {name} is the name of the feature. Currently, AS1600 defines the following features: Feature Name Description -------------------- ---------------------------------------------------- MACRO Support for macros CFGVAR Support for the CFGVAR directive SRCFILE Support for the SRCFILE directive CLASSIFY Support for the CLASSIFY operator TODAY Support for the TODAY_xxx operators ROTATE Support for the _ROTxxx operators EXPMAC Support for the _EXPMAC operator OVERWRITE Support for ERR_IF_OVERWRITTEN / FORCE_OVERWRITE