WARNING:  THIS IS STILL BEING UPDATED FOR REV 3.

==============================================================================
  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://www.primenet.com/~im14u2c/intv/franky.zip

Have fun.  Questions, problems:  im14u2c@primenet.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:

 -- 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\dir\2"

    It can be set under UNIX and Linux by simple assignment:

        AS1600_PATH="C:\path\to\dir1;C:\path\to\dir\2"

    Bourne shell users may need an additional statement:  export AS1600_PATH 

 -- 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
==============================================================================

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?
------------------------------------------------------------------------------
