ZX-IDE Tutorial for programming assembler and ZX BASIC

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Listing

Post by PokeMon »

So today two new lessons. :mrgreen:

During testing or for documentation a program listing would be very helpful. This can be created by simply keypress CTRL-F8 or via the Run menu and changed source will be recompiled automatically. To be more detailed, the function for generating a symbol file will be executed and after a program for creating the listing from the symbol file is started (LISTZ80.EXE) and the output is opened as a new tab in the IDE and saved to an output file <source>.lst. Everybody who wants to do more with this symbol file (which holds very detailed information about the assembling) should refer to the documentation on the flatassembler board:
http://flatassembler.net/docs/fas.zip

Lets take a look at the compiled source (ZX81DEMO.ASM):

Code: Select all

    format binary as "p"
    ;LISTOFF

            ; hardware options to be set and change defaults in ZX81DEF.INC
            MEMAVL     EQU     MEM_16K          ; can be MEM_1K, MEM_2K, MEM_4K, MEM_8K, MEM_16K, MEM_32K, MEM_48K
                                               ; default value is MEM_16K
            DFILETYPE  EQU     COLLAPSED       ; COLLAPSED or EXPANDED
            STARTUPMSG EQU    'CREATED WITH ZX81-IDE' ; any message will be shown on screen after loading, max. 32 chars

            include 'SINCL-ZX\ZX81.INC'           ; definitions of constants
    ;LISTON

    AUTORUN:
            REM 1,asmcode_st,asmcode_end

    asmcode_st:
    ; ##################################################
            ; place own assembler code here
    ; ##################################################

            LD      BC,$55
            RET

    ; ##################################################
            ; end of section for own assembler code
    ; ##################################################
    asmcode_end:

            db      NEWLINE

    LINE2:  ; this is a static PRINT USR 16514 command
            db      0,2,$0E,0,$F5,$D4
            dbzx    '16514'
            db      $7E,$8F,1,4,0,0,NEWLINE

    ;LISTOFF
            include 'SINCL-ZX\ZX81POST.INC'          ; include D_FILE and needed memory areas

    assert ($-MEMST)<MEMAVL
    ; end of program
The listing covers the complete source with all include files. Such a program can be quite long, so you can restrict the listing to several "wanted" parts of the source. This is more clear for the programmer and he need to take a look only the subpart on which he's working on. There are two directives which switch the listing on and off.

Code: Select all

    ;LISTON
    ;LISTOFF
The semicolon at the beginning is important and this directive has to be always at the very first beginning of a line. The default is LISTON when the program starts and these directives can appear at any time in the listing to switch on and off several parts. So the listing is looking like this:

Code: Select all

                                            format binary as "p"
                                            ;LISTON
                                           
                                            AUTORUN:
    0074: [407D] 00 01 06 00 EA                     REM 1,asmcode_st,asmcode_end
                                           
                                            asmcode_st:
                                            ; ##################################################
                                                    ; place own assembler code here
                                            ; ##################################################
                                           
    0079: [4082] 01 55 00                           LD      BC,$55
    007C: [4085] C9                                 RET
                                           
                                            ; ##################################################
                                                    ; end of section for own assembler code
                                            ; ##################################################
                                            asmcode_end:
                                           
    007D: [4086] 76                                 db      NEWLINE
                                           
                                            LINE2:  ; this is a static PRINT USR 16514 command
    007E: [4087] 00 02 0E 00 F5 D4                  db      0,2,$0E,0,$F5,$D4
    0084: [408D] 1D 22 21 1D 20                     dbzx    '16514'
    0089: [4092] 7E 8F 01 04 00 00 76               db      $7E,$8F,1,4,0,0,NEWLINE
In the left column you will find the address (offset) in the program file <source>.p - in the case somebody like to patch from time to time. :mrgreen:
The listing begins with offset $74, this is the BASIC section. The system variables are not shown due to a ;LISTOFF directive before. More important than the offset in the output file is the next column which has the target address or effective address. The code has to be loaded at this address before execution which is done through the LOAD command of the ZX81. The third column shows the generated code and the last column the source line. All addresses are shown hexadecimal. As everybody knows, the code in the first rem line is started with RAND USR 16514 or PRINT USR 16514 (there are still more variants). The decimal address 16514 is hexadecimal $4082 and you can find this address in the listing in the first assembler line "LD BC,$55".

You can simply see that the Z80 processors operates in Little-Endian Format with the least significant byte first.
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Addressing Spaces I

Post by PokeMon »

Now I will go into more details of the flatassembler when generating code and options to use. Several programs have similar options but there are differences from tool to tool also. To get the best of the flatassembler engine I want to go in more details of code generation and not in Z80 assembler programming. So more general hints. If we are talking about address spaces the flatassembler engine supports many different formats like ELF, PE or COFF which are not very useful for the Z80 or ZX81. The operating system supports only flat file format with sequential binary output. There will be only one start point and one end point.
The format can be choosen with the format directive in ZX81DEMO.ASM:

Code: Select all

    format binary as "p"
Binary is the default and obselete but creates an output file with name <source>.bin. With the additional as "p" you can define another extension to use. For the ZX80 it could be used as:

Code: Select all

    format binary as "o"
The new version supports the formats ZX81 (and later ZX80) to choose the extension .p and .o automatically and control the charset used to translate from ASCII to ZX charset.

Code: Select all

    format zx81
- or -
    format zx80
The format statement has to be in the first line of the source.

This p-File for the ZX81 is loaded after the LOAD command at address 16393 or hexadecimal $4009. This is the variable section followed from the BASIC program area.

Code: Select all

                                                    ORG     SAVE_BEGIN
                                           
    0000: [4009] 00                                 VERSN   db 0            ; version, 0=ZX81 BASIC
    0001: [400A] 00 00                              E_PPC   dw 0            ; number of current line with cursor (in listing)
    0003: [400C] 99 40                              D_FILE  dw DFILE_ADDR   ; begin of D_FILE (display file) in memory
    0005: [400E] 9A 40                              DF_CC   dw DFILE_ADDR+1 ; current cursor position in D_FILE (used for print routine)
    0007: [4010] CC 40                              VARS    dw VARS_ADDR    ; begin of variable section in memory
    0009: [4012] 00 00                              DEST    dw 0            ; address of variable in assignment
    000B: [4014] CD 40                              E_LINE  dw WORKSPACE    ; address of workspace
So the first byte of the program is loaded at address $4009 and this has to be told to the assembler. The definition of the runtime address is necessary. If I want to access the variable D_FILE in the program with

Code: Select all

    LD BC,D_FILE
I would get the address $0003 (fourth byte in output file). For this translation the statement ORG is used, followed by the address where the code has to be loaded or should run at.

Code: Select all

    ORG     SAVE_BEGIN
SAVE_BEGIN is a variable, which is declared somewhere else in the source but could be a direct value too like

Code: Select all

    ORG     $4009
    ORG     16393
ORG can be used any number of times in the source but makes only sense, if you organize the relocation of this code to the specified address. ORG specifies only the address where the following code should run, it does not use any filling bytes. There are implemented on request the directives PHASE and DEPHASE which have the same function as ORG but change the address context only temporarily. Here an example:

Code: Select all

                                        format binary as "p"
                                        
                                                org     $4000
                                        
0000: [4000] 21 0E 40                   LD      HL,reloc_start
0003: [4003] 11 00 20                             LD      DE,$2000
0006: [4006] 01 03 00                             LD      BC,reloc_end-reloc_start
0009: [4009] ED B0                                LDIR
000B: [400B] C3 11 40                             JP      weiter
                                        
                                          reloc_start:
                                                  PHASE   $2000
000E: [2000] 78                                   LD      A,B
000F: [2001] 81                                   ADD     A,C
0010: [2002] C9                                   RET
                                                  DEPHASE
                                          reloc_end:
                                        
                                          weiter:
0011: [4011] 3C                                   INC     A
0012: [4012] 05                                   DEC     B
The program has a small relocator code which copies the following code to address $2000 and after switch back to address $4011. In my opinion it is not recommendable to define code blocks this way, a better alternative would be to define all relocation blocks separate at the end of the program. Internal PHASE will do simply an ORG and store the actual address context and restore it with DEPHASE directive. PHASE can be nested in up to 32 levels.

Here a nice example what you could do with PHASE and DEPHASE. :mrgreen:

Code: Select all

                                            format binary as "p"
                                           
                                            lab1:
    0000: [0000] 41 42 43 44 45 46 47 78            db 'ABCDEFGx'
                                            lab2:
    0008: [0008] 26 27 28 29 2A 2B 2C               dbzx 'ABCDEFG'
                                           
                                                    org $4000
                                           
                                            label1:
    000F: [4000] 03                                 INC BC
    0010: [4001] C3 00 40                           JP label1
    0013: [4004] FF FF                              dw -1
                                            label1e:
                                           
                                                    phase $5000+2*10+label1e-label1
                                            label2:
    0015: [501A] 13                                 INC DE
    0016: [501B] C3 1A 50                           JP label2
                                            label2e:
                                                    phase $6000
    0019: [6000] 00                                 db 0
                                                    phase $7000
    001A: [7000] 00 00                              dw 0
                                                    phase $8000
    001C: [8000] 00 00 00 00                        dd 0
                                                    phase $9000
    0020: [9000] 00 00 00 00 00 00 00 00            dq 0
                                           
                                                    dephase
    0028: [800C] 00 00 00 00 00 00 00 00            dq 0
                                                    dephase
    0030: [7016] 00 00 00 00                        dd 0
                                                    dephase
    0034: [601B] 00 00                              dw 0
                                                    dephase
    0036: [503B] 00                                 db 0
                                            label3:
    0037: [503C] 23                                 INC HL
    0038: [503D] C3 3C 50                           JP label3
                                            label3e:
There are more techniques with so called virtual address spaces. I will explain that later here in the tutorial.
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Data definitions I

Post by PokeMon »

Additional to assembler statements there are data structures quite often necessary. The flatassembler alias ZX-IDE has several options for that. There are also many data definitions for 32 bit and 64 bit targets (x86) which may not as useful for the ZX81 (double words or quad words). In most cases you will need only bytes and words with the Z80 cpu.

Code: Select all

    db = 8bit (define byte)
    dw = 16bit (define word)
So here are some declarations of variables from ZX81DEMO.ASM (listing):

Code: Select all

    0011: [401A] CF 40                              STKBOT  dw WORKSPACE    ; stack botton (top-down)
    0013: [401C] CF 40                              STKEND  dw WORKSPACE    ; end of stack (top)
    0015: [401E] 00                                 BERG    db 0            ; calculators b register, possibly write error, should be BREG :-)
    0016: [401F] 5D 40                              MEM     dw MEMBOT       ; address for calculator's memory
    0018: [4021] 00                                 UNUSED1 db 0            ; unused variable / space
    0019: [4022] 02                                 DF_SZ   db 2            ; no. of lines in lower part of screen including 1 blank line
    001A: [4023] 00 00                              S_TOP   dw 0            ; no. of top program line in automatic listings
    001C: [4025] 00 00                              LAST_K  dw 0            ; last key pressed
    001E: [4027] 00                                 DEBOUNCE db 0           ; debounce status of keyboard
    001F: [4028] 37                                 MARGIN  db PAL          ; margin value, could be used PAL or NTSC
You can put several values to db or dw to define more than one dates:

Code: Select all

    007E: [4087] 01 02 03                           db      1,2,3
    0081: [408A] 04 00 05 00 06 00                  dw      4,5,6
Negative values can be used as well and will be created in the used data width, -1 creates $FF as byte and -4 creates $FFFC as word for example.

Code: Select all

    0087: [4090] FF FE FD                           db      -1,-2,-3
    008A: [4093] FC FF FB FF FA FF                  dw      -4,-5,-6
Additional to decimal values you can use declarations in C or Pascal style, octal or binary as well.
Following listing shows different declarations for the same date 255 or $FF.

Code: Select all

    0090: [4099] FF                                 db      255
    0091: [409A] FF                                 db      $ff
    0092: [409B] FF                                 db      0ffh
    0093: [409C] FF                                 db      0xff
    0094: [409D] FF                                 db      377o
    0095: [409E] FF                                 db      11111111b
    0096: [409F] FF                                 db      %11111111
Important is a digit at the beginning or $ for hexadecimal or % for binary values. Hexadecimal declarations with h at the end need a trailing 0 if they begin with a character, otherwise would be interpreted as a symbol or label. There is a helpful tool in the IDE (menu help) to automatically translate a value to decimal/hex/binary/octal - try it out. ;)

You can put character as data definition (db) as well or strings:

Code: Select all

    0097: [40A0] 2E                                 db      '.'
    0098: [40A1] 29                                 db      ')'
    0099: [40A2] 41 42 43 44 45 46 47               db      'ABCDEFG'
    00A0: [40A9] 1B                                 dbzx    '.'
    00A1: [40AA] 11                                 dbzx    ')'
    00A2: [40AB] 26 27 28 29 2A 2B 2C               dbzx    'ABCDEFG'
db defines a character in standard ASCII charset, dbzx defines it in the zx charset. This way you can easily use clear text in your source to be defined as ZX characters. To use character translation for data in instructions you can use double comma instead of single comma. The character will be interpreted as ZX character with double comma.

Code: Select all

    00A9: [40B2] 3E 2E                              LD      A,'.'
    00AB: [40B4] 3E 1B                              LD      A,,'.'
It is possible to mix numbers, characters and strings with db:

Code: Select all

    00A9: [40B2] 45 72 72 6F 72 20 6D 65            db      'Error message',0dh,0adh
                 73 73 61 67 65 0D AD       
    00B8: [40C1] 27 31 2E 33 29 39 2A 3D            dbzx    'Blindtext',0
                 39 00                     
Here you have a string with a trailing character like null or carriage return / line feed in ASCII.
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Data definitions II (blocks & more)

Post by PokeMon »

Sometimes you want to declare a larger block of data. There are several ways to do. First you can simply reserve space with "reserve bytes" (rb) analog to "data bytes". These bytes are filled with "0" in binary flat file format, other formats just define memory areas but do not initialize them.

Code: Select all

    007E: [4087] 01 55 00                           LD      BC,$55
    0081: [408A] C9                                 RET
    0082: [408B] 00 00 00 00 00 00 00 00            rb      16
                 00 00 00 00 00 00 00 00   
    0092: [409B] 00 00 00 00 00 00 00 00            rw      16
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
Here are defined first 16 bytes (rb) and after 16 words (rw).
There is another way to initialize a block with another date than 0:

Code: Select all

    00B2: [40BB] 76 76 76 76 76 76 76 76            db      25 dup(NEWLINE)
                 76 76 76 76 76 76 76 76   
                 76 76 76 76 76 76 76 76   
                 76                         
This would create a so called "collapsed" D_FILE (25 newlines).
But you can also define byte sequences:

Code: Select all

    00CB: [40D4] 01 02 03 04 05 01 02 03            db      5 dup (1,2,3,4,5)
                 04 05 01 02 03 04 05 01   
                 02 03 04 05 01 02 03 04   
                 05                         
    00E4: [40ED] 3F 3D 24 1D 3F 3D 24 1D            dbzx    4 dup ('ZX81')
                 3F 3D 24 1D 3F 3D 24 1D   
    00F4: [40FD] 00 40 00 40 00 40 00 40            db      $16 dup (0,$40)
                 00 40 00 40 00 40 00 40   
                 00 40 00 40 00 40 00 40   
                 00 40 00 40 00 40 00 40   
                 00 40 00 40 00 40 00 40   
                 00 40 00 40               
    0120: [4129] 00 00 00 00 00 00 00 00            db      32 dup(0),NEWLINE
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 76                         
First example creates 5 times the bytes 1,2,3,4,5
Second example creates 4 times the text "ZX81".
Third example initializes a memory area with a word, address $4000.
An no. four creates an empty line in the D_FILE (32 spaces plus one NEWLINE).

It is possible to use data from an external file as well with the file directive:

Code: Select all

    0141: [414A] 21 00 20 11 00 20 01 03            file    '1.p'
                 00 ED B0 C3 11 40 78 81   
                 C9 3C 05                   
    0154: [415D] 40 3C 28 28 ED 58 F2 4A            file    'FASTLOAD.BIN':$40,32
                 40 FE 08 3F CB 12 30 EB   
                 A0 28 06 23 72 04 50 18   
                 E2 05 AE 7A 16 01 E2 6F   
First example reads the file "1.p" with full data at current position in the code.
Second example reads the file "FASTLOAD.BIN" but only 32 bytes at offset $40 from file.

Lets talk about alignment, also important. Sometimes you want to create data areas in portions and want some filling bytes at the end if needed. Typical alignments are due to cpu requirements a value of 2 if a cpu reads a word (16 bit) with one memory access on the databus. If you have to read or write data to an odd address it would require two memory access cycles and the non needed data is thrown away. If you read a word from $4001 it would first read a word from $4000 and throw the first byte and after read a word from $4002 and throw the last byte. Not efficient. So you can define to let the following data begin at an even address with align 2. If needed it will add a nop.

For a 32bit cpu you would choose an alignment of 4. Usual alignments are also paragraphs (16 bytes) from historical reasons as you workd with segment and offset registers in the x86 cpu. But also modern 64 bit cpus require explicit alignments and can throw exceptions when accessing unaligned data.

Here 2 examples for the Z80:

Code: Select all

    0154: [415D] 40 3C 28 28 ED 58 F2 4A            file    'FASTLOAD.BIN':$40,32
                 40 FE 08 3F CB 12 30 EB   
                 A0 28 06 23 72 04 50 18   
                 E2 05 AE 7A 16 01 E2 6F   
    0174: [417D] 00 00 00                           align   $10
    0177: [4180] 00 00 00 00 00 00 00 00            align   $100
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
                 00 00 00 00 00 00 00 00   
First I declared a 16 byte alignment with align $10. The program is at address $417D and next paragraph would be $4180, so it will be filled with 3 nops. After I choosed a second alignment with $100 for filling a 256 byte block. The program is at address $4180 and to get to the next alignment there will be inserted $80 nops.

Another example:

Code: Select all

                                                    PHASE   0
    01F7: [0000] C3 20 42                           JP      somewhere
    01FA: [0003] 00 00 00 00 00                     align   8
    01FF: [0008] C3 20 42                           JP      somewhere
    0202: [000B] 00 00 00 00 00                     align   8
    0207: [0010] C3 20 42                           JP      somewhere
    020A: [0013] 00 00 00 00 00                     align   8
    020F: [0018] C3 20 42                           JP      somewhere
    0212: [001B] 00 00 00 00 00                     align   8
                                                    DEPHASE
Here alignment is used to fill the table with the restart vectors (RST0, RST8, ...).
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Variables and Constants

Post by PokeMon »

For processing of code you need often variables or constants. With variables I mean not storage for use in program but variables controlling assembly process. They are quite not as effective as macros but more easy to handle. With constants I mean symbolic constants. Flatassembler handles constants in general in same way as variables and can be redefined and the name symbolic "constants" has only historical reasons.

The popular form are EQU (equates) and DEFINE's.
The original ROM listing from Wearmouth shows following definition:

Code: Select all

    ; $3F - Character: 'Z'          CHR$(63)

            DEFB    %00000000
            DEFB    %01111110
            DEFB    %00000100
            DEFB    %00001000
            DEFB    %00010000
            DEFB    %00100000
            DEFB    %01111110
            DEFB    %00000000

    .END                                ;TASM assembler instruction.
DEFB isn't know from Borland's Turbo Assembler TASM, it uses .BYTE for byte definition or .WORD for word definition. If you want to process a source with another assembler you have give following additional definitons:

Code: Select all

    #define DEFB .BYTE      ; TASM cross-assembler definitions
    #define DEFW .WORD
    #define EQU  .EQU
This tells the preprocessor to change the term DEFB to .BYTE prior to starting assembly. This example is suitable for Borland but for the ZX-IDE (or flatassembler) you need following defines:

Code: Select all

    define DEFB db
    define DEFW dw
    define EQU  .EQU
    define .END
The flatassembler doesn't know #define but define and the last definition for TASM (.END) is cleared through an empty string. This assembler doesn't use an end statement as it ends automatically at end of source. There is another directive for syntax adjustment with fix which couldn't be realized with EQU. Fix should be used rarely and only in case of name collisions which needs much work on source. Here an example:

Code: Select all

    0082: [408B] 00 00 00 00 00 00 00 00            rb      16
                 00 00 00 00 00 00 00 00   
                                                    EQT     fix EQU
                                                    DEFB    EQT db
    0092: [409B] 01                                 DEFB    1
Here the preprocessor directive EQU is renamed in EQT. DEFINE and EQU do definetely the same but have different syntax. Define need first the old term followed by the new term and EQU is in the middle of old and new term. Just according to taste.

Code: Select all

                                                    define  DEFB db
    0092: [409B] 41                                 DEFB    'A'
                                                    DEFB    EQU dbzx
    0093: [409C] 26                                 DEFB    'A'
This example shows that DEFB is not really a symbolic constant but can be redefined and used as variable. First it uses ASCII charset and after it uses ZX charset. The main difference to assembly time variables is the missing forward declaration due to stringent processing in one pass. The "constant" must be defined before it's content is used.

Next example shows how to handle assembly variables:

Code: Select all

    0094: [409D] 28                                 db      var1
    0095: [409E] 00                                 db      0
                                                    var1=$28
Here the variable var1 is used before definition or defined after first use. This is possible due to multiple passes and neccessary for jumps and calls. The above example needs minium 2 passes to resolve the source which can be read at end of assembling in the "control and information window". There are cases which can not be resolved at all. :mrgreen:

Code: Select all

    var1=var2+1
    var2=var1
Basically a variable can be redefined (used for if-else constructions for example):

Code: Select all

            db      var1
            db      0
            var1=$28
            db      var1
            var1=$29
The redefine is possible only if the variable was not used before. The sample ahead throws the error message "symbol 'var1' out of scope". If the first line is deleted or commented out the assembler can handle this redefinition. The problem is the forward reference. This kind of redefinition is possible with EQU from the preprocessor:

Code: Select all

                                                    var1    EQU     $28
    0094: [409D] 28                                 db      var1
                                                    var1    EQU     $29
    0095: [409E] 29                                 db      var1
The reason for possibility is here the stringent use in sequentiell processed source during preprocessor. If you understand the difference between preprocessor and assembler the use of assignments like EQU or "=" are getting more clear. This is a beginners problem due to the logic and will be much more difficult using macros. On the other hand macros are really powerful in code generation.

In general EQU should be used for static assignments and variables for dynamic assignments. But sometimes vice versa could be useful as well.
Here is a typical example of EQU in source:

Code: Select all

                                                    NO      EQU     0
                                                    YES     EQU     1
                                                    NEWLINE EQU     $76     ; character for NEWLINE
                                                    SPACE   EQU     0       ; character for SPACE
Here are constants used (symbolic names) for specific values like NEWLINE or SPACE. You will remember more easy at the term NEWLINE than at it's value (118 decimal or $76 hexadecimal). On the other hand you can make software more portable to other systems. This could be a similar declaration for DOS or WIN system:

Code: Select all

                                                    NO      EQU     0
                                                    YES     EQU     1
                                                    NEWLINE EQU     $0D,$0A     ; character for NEWLINE
                                                    SPACE   EQU     $20       ; character for SPACE
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

Labels

Post by PokeMon »

Labels are neccessary quite often additional to variables and constants. Labels are used during assembly phase only, the preprocessor doesn't take notice of them. Labels allow forward referencing for jumps and calls. The address can be determined only in one (or maybe more) additional pass (resolving process of assembler). A label can be used arbitrary in front of an instruction or data definition. In the last case it can be used as program variable.

Code: Select all

                                            main_begin:
    007E: [4087] 3A 8E 40                           LD      A,(var1)
    0081: [408A] 21 8F 40                           LD      HL,var2
    0084: [408D] C9                                 RET
    0085: [408E] 10                         var1    db      $10
    0086: [408F] 76                         var2    db      NEWLINE
                                            main_end:
    0087: [4090] 09 00                              dw      main_end-main_begin
The labels main_begin and main_end are usual labels to jump at.
var1 and var2 are labels for variables.
The instruction "LD A,[var1]" uses the variable and load it's content into register A.
The instruction "LD HL,var2" uses the address of var2 instead of it's contents.
The compiler checks during assembly phase if the operand size is valid (8 bit or 16 bit). So the content of HL can not be read directly as it points to a byte instead of a word. But the variable could be accessed during a following "LD A,(HL)".

The last line in the example ahead determines the size of the assembled block (including variables) in bytes (9). You can form any numeric expression with labels. ;)

There are specific assembler variables with the names $ and $$:

Code: Select all

    0089: [4092] 92 40                              dw      $
    008B: [4094] 09 40                              dw      $$
    008D: [4096] 8D 00                              dw      $-$$
$ is the current address of the code (runtime address), in example $4092.
$$ is the current origin, $4009, this is the used ORG address or the address to which the code is loaded from ZX81.
The current position in output file can be determined with $-$$ or how many bytes the program spans.

So the label main_end is obselete in the first example when $ is used for calculation:

Code: Select all

                                            main_begin:
    007E: [4087] 3A 8E 40                           LD      A,(var1)
    0081: [408A] 21 8F 40                           LD      HL,var2
    0084: [408D] C9                                 RET
    0085: [408E] 10                         var1    db      $10
    0086: [408F] 76                         var2    db      NEWLINE
    0087: [4090] 09 00                              dw      $-main_begin
The labels defined ahead are global and can appear only once in the whole sourcecode. In some cases this is not convenient because you have to find permanent new names for variables or jump targets. Hier local labels or variables could become useful and can appear several times in the source and assigned the last global label ahead:

Code: Select all

                                            main_begin:
    007E: [4087] 21 8B 40                           LD      HL,.var1
    0081: [408A] C9                                 RET
    0082: [408B] 10                         .var1    db     $10
                                            main_2:
    0083: [408C] 21 90 40                           LD      HL,.var1
    0086: [408F] C9                                 RET
    0087: [4090] 20                         .var1    db     $20
                                            main_3:
    0088: [4091] 21 8B 40                           LD      HL,main_begin.var1
    008B: [4094] C9                                 RET
    008C: [4095] 30                         .var1   db      $30
In the example .var is defined as local variable, first in section main_begin and after in sections main_2 and main_3.
The instruction LD HL,.var1 uses always the "own" local variable according to the global label. Technical the variable or label get the name <global>.<local>
This property is used in the third instruction with HL register and loads explicitly the .var1 from other section main_begin.
Imaginable are also jumps or calls with JP global.local or CALL global.local

The third alternative are anonymous labels for short jumps in a program. If you want to jump simply a few lines above or ahead you don't need a specific name for the target. Numbering of labels could cause errors. For this purpose the anonymous labels are welcome. ;)

Code: Select all

                                            main_3:
    0088: [4091] 21 8B 40                           LD      HL,main_begin.var1
    008B: [4094] C9                                 RET
    008C: [4095] 30                         .var1   db      $30
                                           
    008D: [4096] 3A 95 40                           LD      A,(.var1)
    0090: [4099] B0                                 OR      B
    0091: [409A] 0E FE                              LD      C,$FE
    0093: [409C] 28 02                              JR      Z,@f
    0095: [409E] 0E F8                              LD      C,$F8
    0097: [40A0] ED 78                      @@:     OUT     A,(C)
    0099: [40A2] 16 08                              LD      D,8
    009B: [40A4] ED 78                      @@:     IN      A,(C)
    009D: [40A6] 17                                 RLA
    009E: [40A7] CB 13                              RL      E
    00A0: [40A9] 10 F9                              DJNZ    @b
Anonymous labels can appear any number of times in source and @f references the following next anonymous label (@@) and @b references the last anonymous label ahead. For these short jumps the labels are really helpful as long as the codeblock is reasonable. A slight risk is the changing target to other anonymous reference when commenting of lines due to change of next or last target. If OUT A,(C) is commented out then the first jump go to IN A,(C) instead or when commenting out IN A,(C) the loop DJNZ will reference the OUT instruction.

Additional to these more obviously risks there can occur problems when you define a new anonymous label in front of another anonymous label. The target window should be small and easy to understand, otherwise local labels are maybe more useful.

There is one more method of defining labels with a directive calles LABEL:

Code: Select all

                                            file_block:
    00A2: [40AB] 40 3C 28 28 ED 58 F2 4A    file    'FASTLOAD.BIN':$40,$40
                 40 FE 08 3F CB 12 30 EB   
                 A0 28 06 23 72 04 50 18   
                 E2 05 AE 7A 16 01 E2 6F   
                 40 BA 28 D7 18 03 BA 20   
                 D2 C3 13 04 ED 79 01 00   
                 00 C3 07 02 00 01 04 00   
                 EA 18 B2 76 00 00 10 00   
                                           
                                            label   adr1 byte at file_block+12
                                            label   adr2 word at file_block+24
    00E2: [40EB] 3A B7 40                           LD   A,(adr1)
    00E5: [40EE] 2A C3 40                           LD   HL,(adr2)
Here labels are assigned to a memory area afterwards. In the example we read a data block from an external file (FASTLOAD.BIN) and want ti assign labels specific offsets. ;)
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

ZX81 BASIC, REM, _asm

Post by PokeMon »

First step in BASIC. With the latest version 1.71.01b.Z80 the ZX-IDE does not support many BASIC commands, only REM and RAND but these are the most important for assembly developers and more commands will be adapted soon. There are several methods to put assembly code into ZX81 programs. The first and most usual way is with a REM line.

Second method would be a string as variable. The disadvantage of this method is the moving start address due to other variables defined or extended. But strings could be place also in a print statement (PRINT "<machine code>"which is not moving as there is no additional line entered before the statement. The execution of the PRINT statement wouldn't make sense but could be avoided in the BASIC program.

Third method is to downsize top of memory (RAMTOP) with the size of program and to start a small loader to put the code there. The advantage is, that the code is resistant against NEW and could be used during several other program loads. Unfortunately it is not resistant against RESET. ;)

The same problem for all methods is to determine the start address of the machine code. This is depending on several factors and could be determined sometimes easier (RAMTOP) but sometimes more difficult (REM line in any line of a program or a variable somewhere in memory). Fix is always the address of a REM statement in the first line of a program. Code would be executed at the well known addres 16514 or $4082. So is the most usual method. So the IDE helps in two situations, reserving needed space for the program in a REM line and determining the start address of that code and not for the first but for any line in BASIC program.

So first we take a look of the standard usage for a REM statement to put comments to a program. The syntax of REM in the ZX-IDE is exactly the same as entering a line directly on a ZX81. Only exception is the definition of machine code, which is done in clear text assembly instructions and not with pseudo symbols like TAN, USR, DIM, graphice characters and more.

Code: Select all

    ;labelusenumeric
    10      REM NUR EIN KLEINES TESTPROGRAMM
    20      REM 123456789
    30      REM INPUT=START-ENDE
    40      REM '(C) by PokeMon'
These are all legal definitions except line 30 which is a bit more critical. Background for that is that the general parsing of the BASIC statements are not in a special statement context but more general. If you look this program on a normal ZX81 (or Emulator=F8) it will look like:
EMU1.jpg
EMU1.jpg (24.64 KiB) Viewed 13583 times
As you see, the "=" character is missing in line 30. Complicated strings should be put in apostrophes '<any formated text with special characters>'. The line ;labelusenumeric is necessary and will be explained later.

There are now additional directives for use with BASIC statements, beginning with an _underline to avoid unwanted name collisions. Look at the next example:

Code: Select all

    10      REM _asm

            LD  A,$10
            LD  B,(HL)
            LD  C ,(IX)
            INC BC
            RET

            END _asm

    20      RAND USR 16514

    30      REM Hauptprogramm BASIC
Here you will find a small assembly program attached to line 20. The assembly block will be started with _asm in a REM line. This block has to be finished before you come back into the BASIC context. If you use PHASE inside of an assembly blocks this PHASE has to be closed same way before returning to BASIC. The compiler throws error messages if an assembly block or a PHASE block has been left open.

So the program would look like this in the emulator:
EMU2.jpg
EMU2.jpg (22.28 KiB) Viewed 13583 times
The advantage is that there can be define assembly blocks of any size with the wanted assembly code very easy.
So the code look like this in the listing (CTRL-F8):

Code: Select all

    0074: [407D] 00 0A 0A 00 EA             10      REM _asm
                                           
    0079: [4082] 3E 10                              LD  A,$10
    007B: [4084] 46                                 LD  B,(HL)
    007C: [4085] DD 4E 00                           LD  C ,(IX)
    007F: [4088] 03                                 INC BC
    0080: [4089] C9                                 RET
                                           
    0081: [408A] 76                                 END _asm
                                           
    0082: [408B] 00 14 0E 00 F9 D4 1D 22    20      RAND USR 16514
                 21 1D 20 7E 8F 01 04 00   
                 00 76                     
                                           
    0094: [409D] 00 1E 15 00 EA 2D 26 3A    30      REM Hauptprogramm BASIC
                 35 39 35 37 34 2C 37 26   
                 32 32 00 27 26 38 2E 28   
                 76                         
Here you can see how BASIC statements are created binary. To be detailed, the additional statement _asm will keep the REM lines open, followed with the assembly code and at last the needed NEWLINE and the correct calculated length.
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

ZX81 BASIC, line numbers, labels

Post by PokeMon »

For the BASIC programming with the ZX-IDE line numbers are obsolete and optional and only available for compatibility to BASIC source. The ZX81 needs line numbers as jump target for GOTO or GOSUB and for editing particular lines. During programming line numbers are obstructive if you work with a modern editor and use copy and paste or want to insert lines or blocks. In the early 80ies with missing complex editors it was usual to work with line numbers in steps of 10 to maybe insert additional lines afterwards.

For the IDE or flatassemblers line numbers are treated as (numeric) labels. Normally numeric labels are not allowed as they couldn't be referenced as parameter and distinguished from real numeric values. So this was a hard part to allow numeric labels but I thought it's necessary to accept the IDE for BASIC development and to program the same way as on the ZX81 in the early 80ies. To make this feature optional I added a pseudo directive ;labelusenumeric which has to be placed on the beginning of a line without any other data or comment. To avoid any side effects this feature could be removed when removing this line and only assembly programming is needed.

Code: Select all

    ;labelautodetect
    ;labelusenumeric
In the previous version there was an additional directive ;labelautodetect which is not necessary any more and not advisable. Normally labels are defined using a colon or the directive LABEL. As this is unusal for BASIC programmers and there are some listings with labels but without colon at the end I created this directive. This is suitable for old or finished listings but not advisable for working on the source as anything written wrong and is not an instruction, directive (wrong statement e.g.) would be interpreted as a label. So typos would maybe not detected and these kind of errors are hard to find. Due to this reason ;labelusenumeric and ;labelautodetect are independent from each other. ;labelusenumeric activates ;labelautodetect but only for numeric labels (starting with a digit).

So now take a look at following example with 5 REM lines and explicitly definition of line numbers:

Code: Select all

    10      REM ZEILE 1
    20      REM ZEILE 2
    30      REM ZEILE 3
    40      REM ZEILE 4
    50      REM ZEILE 5
This would be the listing:

Code: Select all

    0074: [407D] 00 0A 09 00 EA 3F 2A 2E    10      REM ZEILE 1
                 31 2A 00 1D 76             
    0081: [408A] 00 14 09 00 EA 3F 2A 2E    20      REM ZEILE 2
                 31 2A 00 1E 76             
    008E: [4097] 00 1E 09 00 EA 3F 2A 2E    30      REM ZEILE 3
                 31 2A 00 1F 76             
    009B: [40A4] 00 28 09 00 EA 3F 2A 2E    40      REM ZEILE 4
                 31 2A 00 20 76             
    00A8: [40B1] 00 32 09 00 EA 3F 2A 2E    50      REM ZEILE 5
                 31 2A 00 21 76             
You can comprehend the line numbers if you are familiar with the structure of a ZX81 BASIC line. The first two bytes are the line number in big endian (most significant byte first) and the next two bytes are the line lenght in Z80 typical little endian format (least significant byte first) followed from the BASIC statement (EA=REM). The reason for using big endian format for the line number is unclear and if anybody knows I would appreciate a posting here. ;)

We see the line numbers in hexadecimal representation:
0A=10
14=20
1E=30
28=40
32=50

If you want to insert a line between 20 and 30 the good looking style in steps of 10 is lost. Furthermore it's additional work to enter line numbers. So this can be done automatically with the ZX-IDE and you can omit the line numbers with the same result. You can choose a different step size with AUTOLINE. For example 5 or 100.

Code: Select all

            AUTOLINE 16
            REM ZEILE 1
            REM ZEILE 2
            REM ZEILE 3
            REM ZEILE 4
            REM ZEILE 5
This way you can write programs with unsual line numbers. The examples shows a step size of 16 or $10. This looks more nice in the listing:

Code: Select all

    0074: [407D] 00 10 09 00 EA 3F 2A 2E            REM ZEILE 1
                 31 2A 00 1D 76             
    0081: [408A] 00 20 09 00 EA 3F 2A 2E            REM ZEILE 2
                 31 2A 00 1E 76             
    008E: [4097] 00 30 09 00 EA 3F 2A 2E            REM ZEILE 3
                 31 2A 00 1F 76             
    009B: [40A4] 00 40 09 00 EA 3F 2A 2E            REM ZEILE 4
                 31 2A 00 20 76             
    00A8: [40B1] 00 50 09 00 EA 3F 2A 2E            REM ZEILE 5
                 31 2A 00 21 76             
It is possible to use AUTOLINE at any time on the program, for example first with step size of 10 and after with step size 100. For the value of 10 AUTOLINE is obsolete as this is the default step size.

Code: Select all

    0074: [407D] 00 0A 09 00 EA 3F 2A 2E            REM ZEILE 1
                 31 2A 00 1D 76             
    0081: [408A] 00 14 09 00 EA 3F 2A 2E            REM ZEILE 2
                 31 2A 00 1E 76             
    008E: [4097] 00 1E 09 00 EA 3F 2A 2E            REM ZEILE 3
                 31 2A 00 1F 76             
                                                    AUTOLINE 100
    009B: [40A4] 00 82 09 00 EA 3F 2A 2E            REM ZEILE 4
                 31 2A 00 20 76             
    00A8: [40B1] 00 E6 09 00 EA 3F 2A 2E            REM ZEILE 5
                 31 2A 00 21 76             
Corresponding line numbers:
0A=10
14=20
1E=30
82=130
E6=230

Automatic and manual line numbers can be used mixed for have standard size of 10 and align important sections to a multiple of 100.

Code: Select all

            REM ZEILE 1
            REM ZEILE 2
            REM ZEILE 3
    100     REM ZEILE 4
            REM ZEILE 5
This is the generated code:

Code: Select all

    0074: [407D] 00 0A 09 00 EA 3F 2A 2E            REM ZEILE 1
                 31 2A 00 1D 76             
    0081: [408A] 00 14 09 00 EA 3F 2A 2E            REM ZEILE 2
                 31 2A 00 1E 76             
    008E: [4097] 00 1E 09 00 EA 3F 2A 2E            REM ZEILE 3
                 31 2A 00 1F 76             
    009B: [40A4] 00 64 09 00 EA 3F 2A 2E    100     REM ZEILE 4
                 31 2A 00 20 76             
    00A8: [40B1] 00 6E 09 00 EA 3F 2A 2E            REM ZEILE 5
                 31 2A 00 21 76             
Corresponding line numbers:
0A=10
14=20
1E=30
64=100
6E=110

The sole rule is that line numbers have to be ascending. It's not valid to manuallly define line numbers less than the previous line number. Otherwise an error message will appear like "line number conflict, check AUTOLINE".

At last a nice example with a cryptic line number generation: 8-)

Code: Select all

            AUTOLINE $-$$
            REM ZEILE 1
            AUTOLINE $-$$
            REM ZEILE 2
            AUTOLINE $-$$
            REM ZEILE 3
            AUTOLINE $-$$
            REM ZEILE 4
            AUTOLINE $-$$
            REM ZEILE 5
EMU3.jpg
EMU3.jpg (28.93 KiB) Viewed 13579 times
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

ZX81 BASIC, RAND, USR

Post by PokeMon »

To not just define assembling code but to start it, you need the second important statement RAND. It's not precise, you need a funtion called USR. If you don't need a return value you can combine RAND USR which doesn't process the return value. Again not precise but it doesn't process the return value visible. In general assembler programs can be startet with any statement with a numeric parameter like PRINT USR <program> or LET L=USR <program>. However, this first version supports only this method with RAND USR. An address to start is needed as parameter:

Code: Select all

    10      REM _asm
            LD  A,$10
            LD  B,(HL)
            LD  C ,(IX)
            INC BC
            RET
            END _asm

    20      RAND USR 16514
This is the code from the above sample:

Code: Select all

    0074: [407D] 00 0A 0A 00 EA             10      REM _asm
    0079: [4082] 3E 10                              LD  A,$10
    007B: [4084] 46                                 LD  B,(HL)
    007C: [4085] DD 4E 00                           LD  C ,(IX)
    007F: [4088] 03                                 INC BC
    0080: [4089] C9                                 RET
    0081: [408A] 76                                 END _asm
                                           
    0082: [408B] 00 14 0E 00 F9 D4 1D 22    20      RAND USR 16514
                 21 1D 20 7E 8F 01 04 00   
                 00 76                     
The entry address 16514 is well known and valid only for assembler code in the very first BASIC line with REM. 16514 is hexadecimal $4082 and is the begin of the code (LD A,$10). If the REM line is not the first line or if you want to have several code lines you can work simply with RAND and labels, which make it easy to calculate addresses:

Code: Select all

            REM _asm
    simple:
            AND A
            LD  DE,$4000
            SBC HL,DE
            RET
    printch:
            LD  A,,'Z'
            RST $10
            LD  A,,'X'
            RST $10
            LD  A,,'8'
            RST $10
            LD  A,,'1'
            RST $10
            RET
    error:
            RST 8
            db  $0A
    reset:
            JP  0
            END _asm

            RAND USR #simple
            RAND USR #reset
            RAND USR #error
            RAND USR #printch
The following program defines 4 entry points for the assembler code in lines 20 - 50. RUN 50 writes the text ZX81 on screen, RUN 40 presents a BASIC error message and RUN 30 - well try it out. :mrgreen:

It is necessary to mark assembly labels with a leading '#' in front, otherwise the label would be interpreted as a BASIC variable. This is necessary for line with BASIC statements only. Within assembly context the labels have to be used without '#' (example JP label).
EMU4.jpg
EMU4.jpg (35.57 KiB) Viewed 13562 times
The above example was without line numbers. Instead of labels the IDE accepts a line number for URS as parameter with an additional '#' in front. So the numeric line number (which can not be used as label directly) is used as label plus an additional offset of 5. So you can spare defining additional labels.

Code: Select all

    10      REM 'Testprogramm USR mit Zeilennummer'

    20      REM _asm
            LD  A,,'Z'
            RST $10
            LD  A,,'X'
            RST $10
            LD  A,,'8'
            RST $10
            LD  A,,'1'
            RST $10
            RET
            END _asm

    30      RAND USR #20#
The listing:

Code: Select all

    0074: [407D] 00 0A 23 00 EA 39 2A 38    10      REM 'Testprogramm USR mit Zeilennummer'
                 39 35 37 34 2C 37 26 32   
                 32 00 3A 38 37 00 32 2E   
                 39 00 3F 2A 2E 31 2A 33   
                 33 3A 32 32 2A 37 76       
                                           
    009B: [40A4] 00 14 0F 00 EA             20      REM _asm
    00A0: [40A9] 3E 3F                              LD  A,,'Z'
    00A2: [40AB] D7                                 RST $10
    00A3: [40AC] 3E 3D                              LD  A,,'X'
    00A5: [40AE] D7                                 RST $10
    00A6: [40AF] 3E 24                              LD  A,,'8'
    00A8: [40B1] D7                                 RST $10
    00A9: [40B2] 3E 1D                              LD  A,,'1'
    00AB: [40B4] D7                                 RST $10
    00AC: [40B5] C9                                 RET
    00AD: [40B6] 76                                 END _asm
                                           
    00AE: [40B7] 00 1E 0E 00 F9 D4 1D 22    30      RAND USR #20#
                 21 21 1F 7E 8F 01 52 00   
                 00 76                     
Here the program in the emulator which calculates the correct address. But pure BASIC programmer and 'Poke'r can calculate 8F 01 52 00 00 in their head. :lol:
EMU5.jpg
EMU5.jpg (31.89 KiB) Viewed 13562 times
The determined address 16553 = $40A9 is the first assembly instruction LD A,,'Z'. By the way you can use the ZX81 charset in instructions as parameter if you use a double comma. This writing with # in front works with labels as well which will add an offset of 5 (lenght of REM command).
Attachments
EMU6.jpg
EMU6.jpg (29.4 KiB) Viewed 13562 times
Last edited by PokeMon on Sun Sep 08, 2013 10:56 pm, edited 4 times in total.
User avatar
PokeMon
Posts: 2264
Joined: Sat Sep 17, 2011 6:48 pm

ZX81 BASIC, format, AUTORUN, _noedit

Post by PokeMon »

Finally I introduce 4 more directives to use:

format defines the output format. In flatassembler are format directives for ELF, COFF, PE, and so on. For the ZX81 the flat file format binary is used. By default the binary output creates an extension with .bin - so ZX81DEMO.BIN. To use another extension it could be used with AS and parameter for extension name.

Code: Select all

    format binary as "p"
For supporting different models with Z80 assembler (BASIC) the format directive was adapted to simply use format zx81 with default extension ".p" and charset of ZX81. There is also the model ZX80 supported with extension .o and ZX80 char table.

Code: Select all

    format zx81
Whoever prefers another extension can combine this with another extension name:

Code: Select all

    format zx81 as "81"

AUTORUN starts the BASIC program automatically at any line number after loading (LOAD ""). AUTORUN is a label which has to be defined directly in front of the line to start with and adapts a system variable. AUTORUN may appear only once in a program. If you don't want the program to be run automatically the statement can be commented out or placed after the last BASIC line.

Code: Select all

            REM _asm
    printch:
            LD  A,,'Z'
            RST $10
            LD  A,,'X'
            RST $10
            LD  A,,'8'
            RST $10
            LD  A,,'1'
            RST $10
            RET
    error:
            RST 8
            db  $0A
            END _asm

            RAND USR #error
    AUTORUN:
            RAND USR #printch
EMU7.jpg
EMU7.jpg (47.94 KiB) Viewed 13563 times
_noedit can create line numbers with 0 which can not be edited any more. This will be used often for the first BASIC line in a program because the address of the line number is well known (static).

Code: Select all

            REM _noedit _asm
    printch:
            LD  A,,'Z'
            RST $10
            LD  A,,'X'
            RST $10
            LD  A,,'8'
            RST $10
            LD  A,,'1'
            RST $10
            RET
    error:
            RST 8
            db  $0A
            END _asm

            RAND USR #error
    AUTORUN:
            RAND USR #printch
EMU8.jpg
EMU8.jpg (29.54 KiB) Viewed 13563 times
_noedit can be used several times to protect more lines in the program. It could be used for all lines with REM and assembler code blocks but in same way for the entry points of code (RAND USR lines). In extreme _noedit can be used for every line. So nothing in the program can be changed. ;) Anyway the program could be started with RUN from the beginning (so from the first line) or with AUTORUN (from any line). But RUN <linenumber> or GOTO <linenumber> can not be used any more as the ZX81 will not find the line numbers. This feature could be used to start a program only after loading (with AUTOLINE) but not to be startet again with RUN or GOTO.

Code: Select all

            REM _noedit _asm
    printch:
            LD  A,,'Z'
            RST $10
            LD  A,,'X'
            RST $10
            LD  A,,'8'
            RST $10
            LD  A,,'1'
            RST $10
            RET
    error:
            RST 8
            db  $0A
            END _asm

            RAND USR _noedit #error
    AUTORUN:
            RAND USR _noedit #printch

            REM _noedit '(C) by PokeMon'
EMU9.jpg
EMU9.jpg (30.24 KiB) Viewed 13563 times
Last edited by PokeMon on Sun Sep 08, 2013 10:57 pm, edited 1 time in total.
Post Reply