AppleIIAsm-Collection/documentation/AppleIIAsm Library Collecti.../0.6.1/30.0 Detailed_Reference_D1_...

72 KiB

Disk 1 : Required Collection and Aliases

PART I: REQUIRED LIBRARY

The first disk in the AppleIIAsm Library includes all of the macros, subroutines, vectors and reserved memory locations required for the rest of the library to function properly. Note that "functioning properly" only applies to unmodified codebases; highly optimized code that makes the library barely recognizable may not use the same standard procedures.

You may consider the files on this disk to provide the overall software architecture for the rest of the library (as outline in the Software Architecture section). However, one of the goals is to be able to remove or alter major components of the architecture--the girders, you might say--in order to lighten the building as a whole while still keeping its basic structure intact. That is, a good deal of effort is done to make this package of collections as easy for beginners to use as possible while also allowing for substantial changes to the architecture that allows for further optimization after the fact.

Required Components

Basically, the following components will be first covered in this chapter, via looking at the header, macros and subroutines:

  • Special memory locations and variables (especially on the zero-page)
  • Macros for checking Literal versus indirect parameters
  • Most common kinds of parameter passing
  • Checking for a string or an address being passed as a parameter

Since this will encompass the same group of macros, the information will be contained in a section of its own However, many more macros have yet to be covered, and will be so largely in alphabetical order. There are times, additionally, when it makes sense to group together subroutines or macros non-alphabetically; this is done on occasion.

The next components are the _PRN and _WAIT macros, alternatives of which are technically found on other disks but are useful enough to also include in the required collection; the subroutines for these will be covered as well. However, following that will be a long series of macros that being with "B," which are versions of 6502 branch instructions that allow for longer calls. These are useful enough to include in the required collection, and take up little enough space in terms of bytes and cycles that their inclusion here is negligible.

Following the branching macros, two macros dedicated to clear the highest and lowest nibbles of the value in .A are included, following then a series of macros beginning with "C" that emulate some of the functionality found in the 65C02 processor. Additionally, a group of mostly unrelated macros are listed, along with their corresponding subroutines, that are most often used for debugging. Then, finally, 8080 and Z80 instruction set macro emulations will be covered briefly, for use by those who have yet to fully internalize the 6502 instruction set.


Required Header File

SUMMARY

Without the required header file, almost nothing in the rest of the collection will function properly. This is because certain areas of memory defined in the header files are used by their collections for holding variable data, referring to zero-page locations, and so on. The LISTING is as follows, with explanations afterwards (beyond the inline comments):

Condition Value
Name File: HEAD.REQUIRED
Type Header File
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Initialize system for use by rest of library
Dependencies none
Bytes 311
Notes Required for nearly every macro or subroutine
See Also none

DETAILS

First we have the file header, which should be the first section of every file regardless of its type. This includes a short description of what the file contains, last update, contact information, and so on. This file header additionally contains the number of bytes the file uses, and this one in particular uses an unusually substantial number of bytes for a header file (in the optimizations appendix, you'll find ways to reduce the number of bytes here).

LISTING 1.0: HEAD.REQUIRED File Heading

*
*``````````````````````````````*
* HEAD.REQUIRED                *
*                              *
* THIS HEADER MUST BE THE      *
* INCLUDED BEFORE ANY OTHER    *
* CODE IN ORDER FOR THE PROPER *
* FUNCTIONING OF ANY LIBRARY.  *
*                              *
* AUTHOR:    NATHAN RIGGS      *
* CONTACT:   NATHAN.RIGGS@     *
*            OUTLOOK.COM       *
*                              *
* DATE:      06-JUN-2021       *
* ASSEMBLER: MERLIN 8 PRO      *
* OS:        DOS 3.3           *
*                              *
* SIZE: 305 BYTES              *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

This head contains many important labels for memory addresses. For instance, RETLEN is a length byte that determines how long the return value is in RETURN. This return length is always initialized upon a return, whether the return value is a single byte or an array. If the value type is a string, then the string length is returned in RETLEN while the actual string data is placed in RETURN. This is important to remember when referring to strings in loops, as very often the initial instinct is to seek the length of the string at byte zero of RETURN. Additionally, there are the VARTAB and PASSTAB locations, which are used for variable storage in the subroutines as well as passing values to a subroutine that cannot be included on the zero page (a rarity, but it happens).

The rest of the header defines certain hooks and variables that the rest of the libraries depend on. The following word and byte parameter passing hooks, however, are likely the most used parts of the zero page in this collection: ZPW1 through ZPW6 are all used primarily for passing word-length values (16-bits) to subroutines (as is used the ZPWSYS location), and have sequential memory locations, whereas ZPB1 through ZPB4 are used for passing single bytes (these do not have sequential free memory locations, and are thus unsuitable for anything but bytes). All of these zero page locations use the small number of memory addresses that go unused by DOS, ProDOS, Applesoft BASIC, Integer BASIC, and the monitor.

Finally, the warm DOS REENTRY hook is defined, which is required to be jumped to at the end of each and every program (for all intents and purposes, anyhow). A few hooks are defined for memory operations, and then we have the MAIN START label, which signals the beginning of the actual code of the program that follows.

LISTING 1.01: HEAD.REQUIRED Source

*
*``````````````````````````````*
* CONSTANTS                    *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
** ZERO PAGE WORD LOCATIONS
*
ZPW1     EQU   $06        ; AND $07
ZPW2     EQU   $08        ; AND $09
ZPW3     EQU   $EB        ; AND $EC
ZPW4     EQU   $ED        ; AND $EE
ZPW5     EQU   $FA        ; AND $FB
ZPW6     EQU   $FC        ; AND $FD
ZPWSYS   EQU   $FE        ; AND $FF
*
** ZERO PAGE BYTE LOCATIONS
*
ZPB1     EQU   $19
ZPB2     EQU   $1E
ZPB3     EQU   $EF
ZPB4     EQU   $E3
*
REENTRY  EQU   $3D0
*
PROMPT   EQU   $33        ; DOS PROMPT CHARACTER
COLDENT  EQU   $03D3      ; COLD ENTRY TO DOS
SRESET   EQU   $03F2      ; SOFT RESET
PRNTAX   EQU   $F941      ; PRINT HEX VALS OF A,X REGISTERS
BELL     EQU   $FBE4      ; RING MY BELL
IOSAVE   EQU   $FF4A      ; SAVE CURRENT STATE OF REGISTERS
IOREST   EQU   $FF3F      ; RESTORE OLD STATE OF REGISTERS
*
BITON0   EQU   $01        ; FOR AND MASKING
BITON1   EQU   $02        ; TO CHECK IF BIT IS ON
BITON2   EQU   $04
BITON3   EQU   $08
BITON4   EQU   $10
BITON5   EQU   $20
BITON6   EQU   $40
BITON7   EQU   $80
*
*``````````````````````````````*
* DEDICATED MEMORY             *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
         JMP   _MAINSTART
*
** 16 BYTES FOR PASSING VARIABLES. THIS WOULD
** NORMALLY HAPPEN WITH ZERO PAGE LOCATIONS, AT
** LEAST IDEALLY, BUT SOMETIMES EXTRA SPACE MIGHT
** BE NECESSARY.
*
PASSTAB  DS    16
*
** 30 BYTES FOR DEDICATED VARIABLE STORAGE. AGAIN, NOT
** ALWAYS NEEDED SINCE MOST VARIABLES ARE HELD ON THE
** ZERO PAGE, BUT SOMETIMES NECESSARY.
*
VARTAB   DS    30
*
** 256 BYTES DEDICATED TO RETURN
** VALUES OF VARIABLE LENGTH; CAN BE
** MODIFIED TO SUIT SMALLER OR LARGER
** NEEDS.
*
RETLEN   DS    1          ; RETURN VALUE BYTE LENGTH
RETURN   DS    255
*
_MAINSTART


Next Up: Required Macros

As stated in earlier sections of this text, each file contains its own heading with information about the content therein; the required macros file is no different. As will all collections in the AppleIIAsm library, we will begin with macros because they are the most used (and most useful) way to interface with the collection of libraries. If a macro uses a particular subroutine, that subroutine will be discussed in relationship to the macro rather than be introduced as a separate entity. First, however, we'll list the macro file header to stress again how such headers will be presented in the future.

LISTING 1.02: MAC.REQUIRED File Heading

*``````````````````````````````*
* MAC.REQUIRED                 *
*                              *
* MACROS USED FOR CORE UTILS   *
* AND LIBRARY ROUTINES. NOTE   *
* THAT NO OTHER LIBRARY WILL   *
* WORK PROPERLY WITHOUT SOME   *
* EXTREME ALTERATIONS WITHOUT  *
* THE INCULSION OF THIS MACRO  *
* FILE AND SUBROUTINES.        *
*                              *
* AUTHOR:    NATHAN RIGGS      *
* CONTACT:   NATHAN.RIGGS@     *
*            OUTLOOK.COM       *
*                              *
* DATE:      06-JUN-2021       *
* ASSEMBLER: MERLIN 8 PRO      *
* OS:        DOS 3.3           *
*                              *
* SUBROUTINE FILES NEEDED      *
*                              *
*  LIB.REQUIRED.ASM            *
*                              *
* MACROS INCLUDED:             *
*                              *
* _AXLIT : IS LITERAL? (REGS)  *
* _AXSTR : IS STRING? (REGS)   *
* _ISLIT : IS LITERAL? (STACK) *
* _ISSTR : IS STRING? (STACK)  *
* _MLIT  : IS LITERAL? (ZERO)  *
* _MSTR  : IS STRING? (ZERO)   *
* _PRN   : PRINT STRING        *
* _WAIT  : GET KEYPRESS        *
* BEEP   : BEEP FOR SOME TIME  *
* CLRHI  : CLEAR HIGH BYTE     *
* DUMP   : DUMP MEMORY         *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

A typical macro file header includes the name of the file at the top, followed by a short paragraph that describes what these macros are for. Then, the (main) author and his or her contact information is listed, along with the last date of revision, the operating system targeted, and the assembler used. Unless this project grow far beyond my original intentions, these will all likely remain the same, save for revision dates.

What follows then is a LISTING of all subroutine files that are used by the macros in the file; luckily, there is only one subroutine file in use for the required library. After that, a LISTING of macros available in the file are listed, with extremely brief descriptions of what they are for; more detailed descriptions can be found in the headers of the macros themselves. A bit strangely, this macro file header contains fewer subroutine LISTINGs that most others and more macro LISTING than most other file headers, as you will see.

Also note that this header carries with it some key variables that are used in the rest of the library. This is usually relegated to the HEAD file of a library, but assembly variables, rather than vectors, are often defined within the file they are used.


Internal Parameter Macros

The first six macros listed in the required library are primarily for the software architecture's use only, although some may find limited use for them outside of the collection itself. As macros, these are likely the most called---and thus most important, in terms of optimization---among all other macros, and deserve special mention as a group, together. Ultimately, these macros are used to determine how parameters are passed to different subroutines, with the associated byte and cycle costs differing widely depending on which macro and method is used. Among any other reasons, then, this is why they happen to be the most used macros.

Yet what the macros really do is still up for grabs. Before describing each on its own, it helps to remember that the macro names are mnemonic: _AXLIT passes variables via .A and .X while checking whether the variable sent is a literal value or not, _AXSTR passes variables (or more correctly, pointers) via .A and .X while determining if the initial parameter is a string or address; _ISLIT checks whether a parameter is a literal and passes the correct value via the stack, whereas _ISSTR does the same with strings, and _MLIT and _MSTR do much the same as the previous macros, except pass values to subroutines via zero page memory dedicated to parameter passing: ZPW1, ZPB1, and so on.

While these all behave much the same, there are some important differences worth noting, thus good reasoning for tearing apart each macro line-by-line. Note that a subroutine always expects a particular macro to pass parameters; if the wrong macro is used, the subroutine will either freeze or return an error. Instead of complete consistency in terms of passing data to subroutines, we have instead opted for efficiency.


THE _AXLIT MACRO

SUMMARY

Condition Value
Name _AXLIT
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2021
Assembler Merlin Pro 8
OS Apple DOS 3.3
Purpose parse macro parameters for subroutine passing
Input ]1 = memory address byte (literal or not) (2B)
Output places appropriate data in .A and .X based on whether parameter is literal
Dependencies none
Flags Destroyed NZ
Cycles 8
Bytes 6
Notes note that literal values are never actually used as literal values, in any macro or subroutine.
See Also _AXSTR _ISLIT _ISSTR _MLIT _MSTR

DETAILS

Let us first look at the _AXLIT macro, as it serves as the first macro alphabetically as well as succinctly illustrates how the other macros work as well. First, just like with everything else, we should start with the heading.

LISTING 1.03: _AXLIT Macro Heading

*
*``````````````````````````````*
* _AXLIT                       *
*                              *
* CHECKS IF PARAMETER IS A     *
* LITERAL OR NOT, AND SETS THE *
* LOW AND HIGH IN .A AND .X.   *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*                              *
* CYCLES: 8, EITHER WAY        *
* BYTES: 6, EITHER WAY         *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

At first, you should notice that each and every macro (and subroutine, for that matter) also includes its own heading, like each file. These headings differ in focus, however: while the name and short description remain the same, there is also a LISTING of parameters that are passed to the macro as well as the number of cycles and bytes a macro will use. In parentheses, the number of bytes and cycles used for every time the macro is called are listed; that this is slightly different than a subroutine. Since a subroutine is listed only once in memory, it only uses the number of bytes listed once, and does not add to the byte count with any further usage. This is primarily why subroutines and macros exist separately: sometimes it costs more to JSR to a subroutine and then RTS back to the main LISTING than it would take to simply write out the entire subroutine and add more bytes. There is far more to it than this, of course, but this is a good foundation for understanding a key difference, especially in terms of optimization.

Note that _AXLIT expects a single parameter, denoted by ]1, and will at least use up 8 cycles and 6 bytes per usage. Also note that I tend to take the pessimists side, as you'll soon see, and would rather add possible cycles or bytes instead of assume fewer of each. One runs into the problem of execution being too slow far more often than one does too fast.

Now let's look at the _AXLIT LISTING itself, explaining both what each line accomplishes as well as some of the more peculiar notations used in the inline comments.

LISTING 1.04: _AXLIT Macro Code

*
_AXLIT   MAC
         IF    #=]1       ;        IF ]1 IS A LITERAL
         LDX   ]1/$100    ; {4C3B} GET HIGH {NZ}
         LDA   ]1         ; {4C3B} GET LOW {NZ}
         ELSE             ;        OTHERWISE, ]1 IS AN ADDR
         LDX   ]1+1       ; {4C3B} SO GET HIGH VALUE {NZ}
         LDA   ]1         ; {4C3B} THEN LOW VALUE {NZ}
         FIN              ;        END IF
         <<<
*

Warning: If you are unfamiliar with how Assembly language is formatted, this is not the best place to learn so; please see an introductory guide before attempting to read this document any further.

This macro, of course, begins by declaring _AXLIT as a macro on the first line the lines are not numbered, so be prepared for some confusion with longer macro and subroutine LISTINGs. The second line uses what is called a "Pseudo Opcode" provided by the Merlin Pro 8 Assembler to test whether the first character of the first and only parameter is a pound sign, which denotes whether the value being sent is a literal value or an address. If this happens to be a literal, then the next line divides the byte's high nibble of the parameter by $100 and stores it in .X, then loads the low nibble in .A.

But why? The reasoning is simple enough: when a literal value is passed as a parameter, it is still treated as an address, but one that indirectly points to another address. This provides all memory locations with the capability of zero-page addresses indirectly pointing to an address. This does not mean that these memory locations can be used as indirect addresses in the source code, but it does mean that user-inputted data (or file-inputted data) can be addressed indirectly without overburdening the zero page. Thus, when "#" is detected as the first character, it is assumed that the actual address being passed is stored in the literal value converted into an address.

If the first character is not a pound sign, however, then the parameter is assumed to be a direct address, and is treated as such: the ELSE statement tells the assembler to load .X with the passed address +1, indicating its higher byte, whereas .A then stores the low byte of the address to be used. The FIN statement acts like a high-level "end if" statement, and the <<< opcode represents the end of the macro. Control is then returned to the line following that which called the macro in the first place which usually JSRs a subroutine that expects address data passed in .A (low byte) and .X (high byte).

A note on inline comment notation

Rather quickly, you may notice some strange notation at the beginning of some lines the inline comments, like the third line's {4C3B}. This notation documents how many cycles and bytes that the line has added to the total (four cycles, three bytes), and the notation allows for the code minifier utility to skip or include this information in the minified version of the macro or subroutine. This notation is also used at the end of a comment to let the user know which status flags are affected. Again, note that in terms of bytes, this means something different depending on whether it is found in a macro or a subroutine: in a macro, the bytes are always added at each call, whereas in a subroutine the bytes are only added once.

There are other inline comment notations that will be encountered later on---most notably, the double-minus, which says to keep the entire line during minification---but we will describe those as they are encountered. A full list of these meta-notations can be found in Appendix XXX.


THE _AXSTR MACRO

SUMMARY

Condition Value
Name _AXSTR
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin Pro 8
OS Apple DOS 3.3
Purpose Check if parameter passed is a literal string or address
Input ]1 = memory address byte (2B)
Output Places correct address in .a (low) and .X (high)
Dependencies _AXLIT
Flags Destroyed NZ
Cycles 11
Bytes 9+ string bytes (every use)
Notes If the parameter is found to be an address, it is further sent to
the _AXLIT macro for more processing.
See Also _AXLIT _ISLIT _ISSTR _MLIT _MSTR

DETAILS

While the _AXSTR macro works much the same as _AXLIT, there are some important differences between testing for string parameters versus literal values, and the _AXSTR macro will even call _AXLIT if the parameter turns out not to be a literal string. Some subroutines (or rather their calling macros) can accept literal string values to make Assembly more "user-friendly" like higher level languages. One primary example of the is printing to the screen: with the _PRN macro, we can write _PRN "hello, world!" and the macro will act as expected: "hello, world!" will be printed to the screen at wherever the current cursor position resides.

One problem with using the _AXSTR macro, however, is that bytes will accumulate rather quickly, turning what should be 1K program into 10K-15K in no time. This is because of the way that the _AXSTR macro works, as well as with _ISSTR and _MSTR. But to understand how this works, let's first look again at the macro header:

LISTING 1.05: _AXSTR Macro Heading

*
*``````````````````````````````*
* _AXSTR                       *
*                              *
* CHECKS IF PARAMETER IS A     *
* STRING, AND IF SO PROVIDES   *
* AN ADDRESS FOR IT. IF NOT,   *
* CHECK IF IT'S A LITERAL, AND *
* STORE THE HI A LO BYTES IN   *
* .A AND .X.                   *
*                              *
* PARAMETERS                   *
*                              *
* ]1 = MEMORY ADDRESS BYTE     *
*    OR STRING                 *
*                              *
* CYCLES: 11                   *
* BYTES: 9 + STRING BYTES      *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

For the most part, there is nothing to worry about here: this is a standard macro header. However, note that in the BYTES section, you'll see "9 + STRING BYTES." This means that for each time _AXSTR is called with a literal string as a parameter, that string is declared somewhere to become part of the byte-length of the total! A 9 byte macro, then, can easily become a 100 byte macro, and when used over and over again, those bytes can add up. Let's see how this works via the source in question:

LISTING 1.06: _AXSTR Macro Code

*
_AXSTR   MAC
         IF    "=]1 ;       .......IF ]1 IS A STRING
         JMP   __STRCNT2  ; {3C3B} SKIP STRING DEC
]STRTMP  STR   ]1         ; {0C1B+} STR DECLARATION
__STRCNT2                 ;        SKIP TO HERE
         LDX   #>]STRTMP  ; {4C3B} GET HIBYTE OF STRING {NZ}
         LDA   #<]STRTMP  ; {4C3B} GET LO BYTE {NZ}
         ELSE             ;        OTHERWISE, ]1 IS ADDRESS
         _AXLIT ]1        ; {8C6B} TEST OF LITERAL OR NOT
         FIN              ;        END IF
         <<<
*

As usual, the first line declares _AXSTR to be a macro. In this macro, however, the second string's pseudo-operation tests whether the first character of the first and only parameter is a quotation mark that delimits a string; note that for using other text delimiters, which is perfectly acceptable in Merlin Pro 8, you would have to duplicate this code to search for those characters as well. The next line is where the macro starts to significantly differ from _AXLIT: if indeed the parameter is a literal string, then a JMP command is initiation to bypass the declaration of a temporary string so that the string data itself is not executed. Then, .X is loaded with the high byte of this string's address, while the low byte is held in .A.

An alternative way to do this, which might be implemented in the near future, is to simply have pre-allocated space for these strings declared by the required header, thus adding 255 bytes and nothing more regardless of how may times the _AXSTR macro is used. This, however, will be a practice in trading bytes for cycles, and such a macro will better belong in the STDIO library.

Beyond that, if the first character is not a quotation mark, the parameter is then tested by _AXLIT to determine whether it is literal or indirect address being passed to the macro. The IF-ELSE opcode is then ended with FIN and the macro is ended with <<<, returning control to main execution and likely running the appropriate subroutine.


THE _ISLIT MACRO

SUMMARY

Condition Value
Name _ISLIT
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2021
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Check if parameter sent is a literal address or not
Input ]1 = memory address byte (2B)
Output none
Dependencies none
Flags Destroyed NZ
Cycles 14
Bytes 8
Notes none
See Also _AXLIT _AXSTR _ISSTR _MLIT _MSTR

DETAILS

_ISLIT, like _AXLIT before it, checks if a parameter is a literal or indirect address pass, then parses the address appropriately for an awaiting subroutine. At this point, it would be fairly pointless to separate the header from the rest of the parameter-parsing routines, as the macros here all behave alike, so we will now list them together before dissecting the code.

LISTING 1.07: _ISLIT Macro Heading and Code

*
*``````````````````````````````*
* _ISLIT                       *
*                              *
* CHECKS IF THE PARAMETER IS   *
* A LITERAL OR NOT, THEN       *
* PUSHES THE LO AND HI AS      *
* NEEDED.                      *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*                              *
* CYCLES: 14                   *
* BYTES:  8                    *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
_ISLIT   MAC
         IF    #=]1       ;        IF ]1 IS A LITERAL
         LDA   ]1/$100    ; {4C3B} GET HIGH BYTE {NZ}
         PHA              ; {3C1B} PUSH .A TO STACK
         LDA   ]1         ; {4C3B} GET LOW BYTE {NZ}
         PHA              ; {3C1B} PUSH .A TO STACK
         ELSE             ;        OTHERWISE, ]1 IS ADDRESS
         LDA   ]1+1       ; {4C3B} SO GET HIGH NIBBLE {NZ}
         PHA              ; {3C1B} PUSH .A TO STACK
         LDA   ]1         ; {4C3B} THEN GET LOW NIBBLE {NZ}
         PHA              ; {3C1B} AND PUSH TO STACK
         FIN              ;        END IF
         <<<
*

_ISLIT differs from _AXLIT primarily in terms of how the address parameters pointers are passed to a subroutine called after the macro is processed. Instead of passing data via the registers, _ISLIT passes data via the stack. Thus, the first line again establishes _ISLIT as a macro, while the second line tests whether the first and only parameter is a literal value. If the value is literal, it is first divided by $100 to get the high byte and then pushes it to the stack, then pushes the low byte to the stack. Otherwise, nearly the same process is made for a nonliteral, except the high byte is literally one byte address above the address passed already; thus, ]1+1, the high byte, is pushed to the stack, followed then by the low byte. These are then read by a following subroutine backwards: low byte first, high byte second. Confusing this process can lead to countless headaches, so be sure to follow them precisely. On the upside, however, passing via the stack allows for many more variables to be passed to a subroutine than either _AXLIT / _AXSTR or the soon to be introduced zero-page memory massing routines.

Like before, FIN acts as an END-IF while <<< indicates the end of the macro. Such instructions will not be explained again for the rest of this section on parameter-passing macros.


The _ISSTR MACRO

SUMMARY

Condition Value
Name _ISSTR
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Checks if a literal string is passed
as a parameter
Input ]1 = memory address byte (2B)
Output none
Dependencies _ISLIT
Flags Destroyed NZ
Cycles 13 or 8
Bytes 10+ or 6
Notes none
See Also _AXLIT _AXSTR _ISLIT _MLIT _MSTR

DETAILS

The _ISSTR macro once again works like the _AXSTR macro, except that again the parameter pointers are passed via the stack rather than the registers. The first parameter is checked whether it is a literal string; if so, the string is then created on-the-fly and a pointer to that address is pushed to the stack, high byte first then low byte. Otherwise, the parameter is assumed to be an address and is again checked to be a literal; if so, the address is sent to _ISLIT for further parsing.

LISTING 1.08: _ISSTR Macro Heading and Code

*
*``````````````````````````````*
* _ISSTR                       *
*                              *
* CHECKS IF PARAMETER IS A     *
* STRING, AND IF SO PROVIDE IT *
* WITH AN ADDRESS. IF NOT,     *
* CHECK IF IT'S A LITERAL AND  *
* PASS ACCORDINGLY.            *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*       OR STRING              *
*                              *
* CYCLES: 13 -OR- 8            *
* BYTES: 10+ -OR- 6            *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
_ISSTR   MAC
         IF    "=]1 ;............. IF ]1 IS A STRING
         JMP   __STRCONT  ; {3C3B} SKIP STRING DEC
]STRTMP  STR   ]1         ; {0C1+B} DECLARE STRING
__STRCONT
         LDA   #>]STRTMP  ; {2C2B} GET HI VAL {NZ}
         PHA              ; {3C1B} PUSH .A TO STACK
         LDA   #<]STRTMP  ; {2C2B} GET LO 2C,2B {NZ}
         PHA              ; {3C1B} PUSH .A TO STACK
         ELSE             ;        OTHERIWSE ]1 IS AN ADDRESS
         _ISLIT ]1        ; {8C6B} CHECK IF LITERAL
         FIN              ;        END IF
         <<<
*

THE _MLIT MACRO

SUMMARY

Condition Value
Name _MLIT
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Checks if parameter is a literal address
then puts appropriate value in zero memory location
Input ]1 = memory address byte (2B)
Output none
Dependencies none
Flags Destroyed NZ
Cycles 16
Bytes 12
Notes none
See Also _AXLIT _AXSTR _ISLIT _ISSTR _MSTR

DETAILS

The _MLIT macro passes address pointer parameters to a subroutine via predetermined locations on the zero page---those defined by WPAR1 through WPAR4 for 16-bit values and those between BPAR1 through BPAR4 for single-byte values. Unlike previous macros, _MLIT now accepts and introduces the concept of two parameters, defined by the symbols ]1 for the first parameter and ]2 for the second. The first parameter should already be familiar: it's the pointer to the address being passed, and may be either a literal value or otherwise. The second parameter, however, is a bit more complicated due to the fact that the address passed there must match the address expected by the subroutine that follows the macro. This is why the locations ZPW1 or ZPB1 are provided in the first place: to provide an easy-to-remember method of passing a variable via zero-page addresses.

Like with the order of passing parameters on the stack, it is extremely important that the same memory locations be used in sending the parameters that the subroutine will be expecting--otherwise, it will be working with the incorrect data, leading to errors or a frozen computer. To pass more than a single parameter, the a semicolon is used to separate the values, as such:

_MLIT #$300;WPAR1

This passes the contents found at the address held in $300 via WPAR1 on the zero page, which corresponds with bytes $06 and $07. By using multiple _MLIT macro calls, one can send a total of 12 bytes worth of parameters via the zero page, which is almost as must as most anything would need. If one needs more than that, they would use the _ISLIT macro.

_MLIT mostly works like the parameter-passing macros before it. The first parameter is checked as to whether it is a literal or not; if it is, then the address is divided by $100 to retrieve the high byte of the address and this is stored in the provided variable location's high byte (say, _MLIT1). The low byte is then copied to the low byte are of _MLIT1. If the parameter is not a literal value, then the first parameter is copied to the second parameter, both low byte and high byte, in a straight-forward manner.

LISTING 1.09: _MLIT Macro Heading and Code

*
*``````````````````````````````*
* _MLIT                        *
*                              *
* CHECKS IF PARAMETER IS A     *
* LITERAL OR NOT, AND SETS THE *
* LO AND HI IN THE SPECIFIED   *
* MEMORY ADDRESS.              *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*  ]2 = ZERO PAGE ADDRESS (2B) *
*                              *
* CYCLES: 16                   *
* BYTES:  12 (EITHER WAY)      *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
_MLIT    MAC
         IF    #=]1       ; .......IF ]1 IS A LITERAL
         LDA   ]1/$100    ; {4C3B} GET HI NIBBLE {NZ}
         STA   ]2+1       ; {4C3B} STORE IN ZP
         LDA   ]1         ; {4C3B} THEN GET LO {NZ}
         STA   ]2         ; {4C3B} STORE IN ZP
         ELSE             ;        OTHERIWSE ]1 IS ADDRESS
         LDA   ]1+1       ; {4C3B} SO GET HIGH NIB {NZ}
         STA   ]2+1       ; {4C3B} AND STORE IN ZP
         LDA   ]1         ; {4C3B} THEN GET LOW NIB {NZ}
         STA   ]2         ; {4C4B} AND STORE IN ZP
         FIN              ;        END IF
         <<<
*

THE _MSTR MACRO

SUMMARY

Condition Value
Name _MSTR
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Check if parameter is a string, then
passes pointer via the zero page
Input ]1 = memory address byte (2B)
Output none
Dependencies _ISLIT
Flags Destroyed NZ
Cycles 15 or 8
Bytes 14+ or 6
Notes none
See Also _AXLIT _AXSTR _ISLIT _ISSTR _MLIT

DETAILS

Lastly, the _MSTR macro is used to pass a string or string address to a subroutine called after invoking the macro, like the other string parameter routines. The difference between this routine and the other string subroutines, of course, is that this uses a second parameter to indicate where on the zero page the address to the appropriate data is stored.

The first parameter is checked firsthand for a preceding quotation mark, signaling that it is a literal string; if so, the string is stored in memory and the pointer to that particular address is then stored in the second parameter address, byte-for-byte. If the first parameter is not a string, this the parameter is then checked by _MLIT and treated appropriately before being finally prepared for passage to a subroutine.

LISTING 1.10: _MSTR Macro Heading and Code

*
*``````````````````````````````*
* _MSTR                        *
*                              *
* CHECKS IF PARAMETER IS A     *
* STRING, AND IF SO PROVIDE IT *
* WITH AN ADDRESS. IF NOT,     *
* CHECK IF IT'S A LITERAL AND  *
* PASS ACCORDINGLY.            *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*       OR STRING              *
*  ]2 = ZERO PAGE ADDRESS (2B) *
*                              *
* CYCLES: 15 -OR- 8            *
* BYTES: 14 +STRING BYTES OR 6 *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
_MSTR    MAC
         IF    "=]1 ;............. IF ]1 IS A STRING
         JMP   __STRCONT  ; {3C3B} SKIP STRING DEC
]STRTMP  STR   ]1         ; {OC1+B} DECLARE STRING
__STRCONT                 ;         CONTINUE
         LDA   #>]STRTMP  ; {2C2B} GET HI NIB OF ADDR {NZ}
         STA   ]2+1       ; {4C3B} STORE IN ZP
         LDA   #<]STRTMP  ; {2C2B} GET LOW NIB {NZ}
         STA   ]2         ; {4C3B} SPRE ON ZP
         ELSE             ;        OTHERWISE ]1 IS ADDRESS
         _MLIT ]1;]2      ; {8C6B} CHECK IF LITERAL
         FIN              ;        END IF
         <<<
*

SUBROUTINE SHORTCUTS

There are times when you will either want to quickly print some text to the screen or wait for the user to press a key before continuing without importing the macros from the STDIO collection. This is especially useful when debugging and writing demo programs for collections separate from STDIO, as you will see. To accommodate this need, the _PRN and the _WAIT macros have been included in the required library. Note, however, that these are severely limited macros and corresponding subroutines; when anything remotely complicated or versatile is necessary, it is advisable to use the STDIO library.

THE _PRN MACRO

SUMMARY

Condition Value
Name _PRN
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose print a literal string to COUT
Input ]1 = memory address byte (2B)
Output string is displayed
Dependencies __P
Flags Destroyed NZCV
Cycles 161+
Bytes 9+
Notes none
See Also __P

DETAILS

The _PRN macro is a quick and dirty method of printing to the screen via COUT. Primarily, this macro is used when the STDIO collection is not being used but some output needs to be put onto the screen, especially in terms of debugging. It accepts a single parameter that is either a literal string---nothing more, nothing less. Note that this macro, as you'll see in the source, can quickly add bytes to your program's total byte size. Let's skip the _PRN macro's header to be closer to the source we are interpreting.

LISTING 1.11: _PRN Macro Heading

*
*``````````````````````````````*
* _PRN                         *
*                              *
* PRINT A STRING OR ADDRESS.   *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*       OR STRING              *
*                              *
* CYCLES: 161+                 *
* BYTES: 9+                    *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

As usual, the first line of the macro defines its name; in this case, _PRN. The second line backs up the value in .Y as a courtesy to the programmer of the calling routine, since .Y is often used as a looping counter most all subroutines follow this same example, though there are a few exceptions. The third line, however, may come as a surprise: the subroutine __P is called, despite apparently no preparations for passing it a string parameter. While this will become more apparent once the __P subroutine is discussed, for now it suffices to say that what is passed to the __P subroutine is the current program counter itself, as it is pushed to the stack by the JSR opcode.

After the subroutine is called, somewhat confusingly at first, an ASC is declared with the string passed as a parameter to the _PRN macro, followed by a HEX byte of $00. .Y is then loaded back with its previously backed up value, and control is returned to the main routine.

LISTING 1.12: _PRN Macro Code

*
_PRN     MAC
         JSR   __P        ; {161C3B} PRINT THE STRING {NZCV}
         ASC   ]1         ; {0C1B+}  HOLD STRING HERE
         HEX   00         ; {0C1B}   KILL STRING PRINT
         <<<
*

Of course, the big question is: how does __P know what to print? Those of you fairly well-versed will immediately recognize the trick--and a dirty trick it is. Let's look at the source code for the __P subroutine to see exactly how this works. In the meantime, this will also be the first subroutine examined of all subroutines in the collection, so we may have to explain a bit more than the subroutine would usually merit.

The source is as follows. First and foremost, of course, we have the subroutine header. This differs slightly from the header of a macro in that it specifies methods of input and output rather than feign any parameters, and the header details any of the status registers it alters or, in the lingo of the header, "destroys". Again also note that since a subroutine is placed into memory only once, unlike a macro, the number of bytes it uses does not increase or multiply on each use.


THE __P SUBROUTINE

SUMMARY

Condition Value
Name __P
Type Subroutine
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose print text to the screen
Input ASC string trailing call to subroutine
Output Literal string on the display
Dependencies none
Flags Destroyed NZCV
Cycles 155+
Bytes 35
Notes none
See Also _PRN

DETAILS

LISTING 1.13: __P Subroutine Heading

*
*``````````````````````````````*
* __P:          (NATHAN RIGGS) *
*                              *
* INPUT:                       *
*                              *
*  ASC STRING FOLLOWING CALL   *
*  TERMINATED WITH A 00 BYTE   *
*                              *
* OUTPUT:                      *
*                              *
*  CONTENTS OF STRING.         *
*                              *
* DESTROYS: NZCIDV             *
*           ^^^  ^             *
*                              *
* CYCLES: 155+                 *
* SIZE: 35 BYTES               *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*

As the INPUT section of the header reveals, this subroutine reads the lines after the call to the __P subroutine---the question is, how? With a little work, we can explain how that happens here.

The first line, __P, declares the start of the subroutine. Afterwards, the first thing that happens is a PLA opcode, which pulls the top value off of the stack and stores it in .A. This is then stored in the low byte of ADDR1, which is, if you don't recall, a 16-bit group on the zero page meant to hold addresses that will be accessed indirectly. Next another byte is pulled from the stack and stored in .A, then transferred to ADDR1 + 1; that is, it is the high byte of ADDR1. Essentially, what has just been pulled from the stack is the address of the instruction that called this subroutine from the macro.

Next, a .Y offset of 1 is established for indirectly accessing the contents off the calling address+1 byte--that is, the ASC data following the call to __P in the macro! This line just so happens to begin with the :LP label so that (As per Merlin's rules, the preceding colon means that it is a local label) .The .Y register can be increased and then the next byte in the string printed. This is repeated until the $00 HEX byte is encountered, at which point the entire string has been printed to the screen.

The loop is then ended, as BNE :LP indicates that as long as .A is not holding $00, the loop should continue. We have hit the $00, so we now continue to the next line, labeled :DONE, where we clear the carry bit. We now transfer the offset value stored in .Y to .A, and add that to the original address we pulled from the stack, effectively skipping the string and #HEX $00 to the next address pointer. This address is then pushed to the stack and JSR is called, an opcode that jumps to the address at the top of the stack.

We have now officially left the subroutine, and returned to the macro---except now the program counter points at the line after HEX $00, the LDY SCRATCH line, which restores the original .Y value before the macro was called. The macro is then ended with <<<, and our first subroutine adventure is complete!

LISTING 1.14: __P Subroutine Source

*
__P
         PLA              ; {4C1B} PULL RETURN LOBYTE {NZ}
         STA   ZPWSYS     ; {3C2B} STORE TO ZERO PAGE
         PLA              ; {4C1B} PULL RETURN HIBYTE {NZ}
         STA   ZPWSYS+1   ; {3C2B} STORE TO ZERO PAGE
         LDY   #1         ; {2C2B} SET OFFSET TO PLUS ONE
:LP      LDA   (ZPWSYS),Y ; {6C2B} LOAD BYTE AT OFFSET .Y
         BEQ   :DONE      ; {4C2B} IF BYTE = 0, QUIT
         JSR   ]COUT      ; {89C3B} OTHERWISE, PRINT BYTE {NZCV}
         INY              ; {2C1B} INCREASE OFFSET {NZ}
         BNE   :LP        ; {4C2B} IF .Y <> 0, CONTINUE LOOP
:DONE    CLC              ; {2C1B} CLEAR CARRY FLAG {C=0}
         TYA              ; {2C1B} TRANSFER OFFSET TO .A {NZ}
         ADC   ZPWSYS     ; {3C2B} ADD OFFSET TO RETURN ADDR {NZCV}
         STA   ZPWSYS     ; {4C2B} STORE TO RETURN ADDRESS LOBYTE
         LDA   ZPWSYS+1   ; {4C2B} DO THE SAME WITH THE HIBYTE {NZ}
         ADC   #0         ; {3C2B} CARRY NOT RESET, SO INC HIBYTE {NZCV}
         PHA              ; {3C1B} IF NEEDED; THEN, PUSH HIBYTE
         LDA   ZPWSYS     ; {4C2B} LOAD LOBYTE {NZ}
         PHA              ; {3C1B} PUSH LOBYTE
         RTS              ; {6C1B} EXIT
*

THE _WAIT MACRO

SUMMARY

Condition Value
Name _WAIT
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose halt execution until a key is pressed
Input none
Output none
Dependencies none
Flags Destroyed NZ
Cycles 14+
Bytes 10
Notes none
See Also none

DETAILS

Thankfully, the next subroutine we'll be exploring is much simpler, as well as the macro that calls it. The _WAIT macro simply calls the __W subroutine, which then loops until a key is pressed on the keyboard or gamepad.

LISTING 1.15: _WAIT Macro Heading and Source

*
*``````````````````````````````*
* _WAIT                        *
*                              *
* WAIT FOR A KEYPRESS.         *
*                              *
* NO PARAMETERS                *
*                              *
* CYCLES: 14+                  *
* BYTES : 10                   *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
_WAIT    MAC
]LP      LDA   ]KYBD      ; {4C3B} CHECK FOR KEYPRESS {NZ}
         BPL   ]LP        ; {4C2B} IF NOT, KEEP LOOPING
         AND   #$7F       ; {2C2B} SET HIGH BIT {NZ}
         STA   ]STROBE    ; {4C3B} RESET KYBD STROBE
         <<<
*

THE BEEP MACRO

SUMMARY

Condition Value
Name BEEP
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Beep Alert sound
Input ]1 = length of beep (1B)
Output send a standard beep to the speaker
Dependencies none
Flags Destroyed NZC
Cycles 108+
Bytes 15
Notes none
See Also none

DETAILS

The last of the quick and dirty macros for error testing is the BEEP macro, which turns out to be a rather self-explanatory title. This macro sends a click to the internal speaker for the specified number of cycles; the longer this lasts, the more annoying it gets. The macro stands alone and does not rely on any subroutines, due to its inherent simplicity.

LISTING 1.16: BEEP Macro and Heading Subroutine

*
*``````````````````````````````*
* BEEP                         *
*                              *
* RING THE STANDARD BELL.      *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = NUMBER OF RINGS (1B)   *
*                              *
* CYCLES: 102+                 *
* BYTES: 11                    *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
BEEP     MAC
         LDX   ]1         ; {4C3B} LOAD BEEP LENGTH {NZ}
]LP1
         JSR   BELL       ; {90C3B} JSR TO BELL ROUTINE
         DEX              ; {2C1B}  DECREASE .X COUNTER {NZ}
         CPX   #0         ; {2C2B}  CMP .X TO ZERO {NZC}
         BNE   ]LP1       ; {4C2B}  IF !=, LOOP
         <<<
*

Miscellaneous Useful Macros

While rather sparse, the Required Macros Library does include a number of independent macros that help with debugging, timing, and so on. They are as follows.

THE CLRHI MACRO

SUMMARY

Condition Value
Name CLRHI
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Clear High nibble of a byte
Input .A = Byte
Output none
Dependencies none
Flags Destroyed NZC
Cycles 10
Bytes 6
Notes none
See Also none

DETAILS

The CLRHI macro clears the high nibble of the byte held in .A.

LISTING 1.17: DELAY Macro and Heading

*
*``````````````````````````````*
* CLRHI                        *
*                              *
* CLEAR THE HIGH NIBBLE OF A   *
* BYTE IN .A REGISTER          *
*                              *
* CYCLES: 10                   *
* SIZE: 6 BYTES                *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
CLRHI    MAC
*
         AND   #$F0       ; {2C2B} CLEAR 4 RIGHT BITS {NZ}
         LSR              ; {2C1B} MOVE BITS RIGHT {ZC,N=0}
         LSR              ; {2C1B} MOVE BITS RIGHT {ZC,N=0}
         LSR              ; {2C1B} MOVE BITS RIGHT {ZC,N=0}
         LSR              ; {2C1B} MOVE BITS RIGHT {ZC,N=0}
         <<<
*

THE DUMP MACRO

SUMMARY

Condition Value
Name DUMP
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Dump value in mem block to screen
Input ]1 = Address (2B)
]2 = Length
Output none
Dependencies __DUMP
Flags Destroyed NZCV
Cycles 811+
Bytes 16
Notes none
See Also __DUMP

DETAILS

It is often useful, and sometimes necessary, to view the contents of a block of memory while trying to debug a given subroutine. The DUMP macro does exactly that: it dumps a specified block of memory to the screen for the user to see, in hexadecimal, before continuing execution of the program. Note that this does not ceate a pause for the information to be absorbed; the pause must be explicitly stated with something like a _WAIT statement. This macro calls the __DUMP subroutine, which handles most of the work.

LISTING 1.18: DUMP Macro and Heading

*
*``````````````````````````````*
* DUMP                         *
*                              *
* DUMP THE HEX AT A GIVEN      *
* ADDRESS.                     *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = MEMORY ADDRESS (2B)    *
*  ]2 = LENGTH IN BYTES (1B)   *
*                              *
* CYCLES: 806+                 *
* BYTES: 12                    *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
DUMP     MAC
         _AXLIT ]1        ; {8C6B}   CHECK LITERAL
         LDY   ]2         ; {4C3B}   LOAD .Y WITH LENGTH {NZ}
         JSR   __DUMP     ; {794C3B} DUMP CONTENTS
         <<<
*

THE __DUMP SUBROUTINE

SUMMARY

Condition Value
Name __DUMP
Type Subroutine
Author Nathan Riggs
Last Revision 06-JUN-2021
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose Dump value in mem block to screen
Input .A = Low byte of address
.X = High byte of address
.Y = Length
Output none
Dependencies none
Flags Destroyed NZCV
Cycles 787+
Bytes 111
Notes none
See Also DUMP

DETAILS

The __DUMP subroutine dumps a given address range of hexadecimal values to the screen, often used for debugging. The Actual hexadecimal values are converted to strings before being printed to the screen, given a starting addres at each line.

LISTING 1.19: DUMP Subroutine

*
*``````````````````````````````*
* __DUMP:       (NATHAN RIGGS) *
*                              *
* INPUT:                       *
*                              *
*  .A = ADDRESS LOBYTE         *
*  .X = ADDRESS HIBYTE         *
*  .Y = NUMBER OF BYTES        *
*                              *
* OUTPUT:                      *
*                              *
*  OUTPUTS DATA LOCATED AT THE *
*  SPECIFIED ADDRESS IN HEX    *
*  FORMAT FOR SPECIFIED NUMBER *
*  OF BYTES.                   *
*                              *
* DESTROYS: NZCIDV             *
*           ^^^  ^             *
*                              *
* CYCLES: 787+                 *
* SIZE: 111 BYTES              *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
__DUMP
         STY   ]LENGTH    ; {4C3B} LENGTH PASSED IN .Y
         STA   ZPWSYS     ; {3C2B} ADDRESS LOBYTE IN .A
         STX   ZPWSYS+1   ; {3C2B} ADDRESS HIBYTE IN .X
         LDA   #$8D       ; {2C2B} LOAD CARRIAGE RETURN {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         LDA   ZPWSYS+1   ; {2C2B} GET ADDRESS HIBYTE {NZ}
         CLRHI            ; {22C13B} CLEAR HIBITS {ZCV,N=0}
         TAX              ; {2C1B} TRANSFER TO .X {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} LOAD HEX CHAR FROM TABLE AT .X {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         LDA   ZPWSYS+1   ; {2C2B} LOAD ADDRESS HIBYTE AGAIN {NZ}
         AND   #$0F       ; {2C2B} CLEAR LOBITS {NZ}
         TAX              ; {2C1B} TRANSER TO .X {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} LOAD CHAR FROM TABLE AT .X {NZ}
         JSR   ]COUT      ; {89C3B} SENT TO COUT {NZCV}
         LDA   ZPWSYS     ; {4C3B} LOAD LOBYTE {NZ}
         CLRHI            ; {22C13B} CLEAR HIBITS {ZCV,N=0}
         TAX              ; {2C1B} TRANSFER TO .X {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} LOAD HEXCHAR AT .X {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         LDA   ZPWSYS     ; {3C2B} LOAD LOBYTE AGAIN {NZ}
         AND   #$0F       ; {2C2B} CLEAR LOBITS {NZ}
         TAX              ; {2C1B} TRANSFER T .X {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} LOAD HEXCHAR AT .X {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         LDA   #":"       ; {2C2B} {NZ}
         JSR   ]COUT      ; {89C3B} SEND COLON TO COUT {NZCV}
         LDA   #"#"       ; {2C2B} {NZ}
         JSR   ]COUT      ; {89C3B} SEND SPACE TO COUT {NZCV}
         LDY   #0         ; {2C2B} RESET COUNTER {NZ}
:LP
         LDA   (ZPWSYS),Y ; {6C2B} LOAD BYTE FROM ADDRESS {NZ}
         CLRHI            ; {22C13B} AT COUNTER OFFSET; CLEAR HIBITS
         STA   ]LEFT      ; {4C3B} SAVE LEFT INDEX
         LDA   (ZPWSYS),Y ; {6C2B} RELOAD {NZ}
         AND   #$0F       ; {2C2B} CLEAR LOBITS {NZ}
         STA   ]RIGHT     ; {4C3B} SAVE RIGHT INDEX
         LDX   ]LEFT      ; {4C3B} LOAD LEFT INDEX {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} GET NIBBLE CHAR {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NCZV}
         LDX   ]RIGHT     ; {4C3B} LOAD RIGHT INDEX {NZ}
         LDA   ]HEXTAB,X  ; {5C3B} GET NIBBLE CHAR {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         LDA   #160       ; {4C3B} LOAD SPACE {NZ}
         JSR   ]COUT      ; {89C3B} SEND TO COUT {NZCV}
         INY              ; {2C1B} INCREASE COUNTER {NZ}
         CPY   ]LENGTH    ; {4C3B} IF COUNTER < LENGTH {NZC}
         BNE   :LP        ; {4C2B} CONTINUE LOOP
         RTS              ; {6C1B} ELSE, EXIT
*

THE GBIT MACRO

SUMMARY

Condition Value
Name GBIT
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose get value of specific bit in byte
Input ]1 = Byte to get bit from
]2 = Bit number
Output none
Dependencies none
Flags Destroyed NZ
Cycles 14
Bytes 15
Notes none
See Also none

DETAILS

The GBIT macro loads .A with the value of a given bit in a given byte. To address which bit to test, the BITON# variables should be used in order to avoid "magic numbers." For instance, GBIT $300;#BITON6 would test whether bit 6 of the byte found in address $300 is either a 0 or 1, which is store in the Accumulator. Remember that the eight bits of a byte start at bit 0 from the right to bit 7 on the left. Therefore, the preceding code would test the second from last bit in the byte.

LISTING 1.20: GBIT Subroutine

*
*``````````````````````````````*
* GBIT                         *
*                              *
* GET BIT FROM REG / ADDR BYTE *
*                              *
* PARAMETERS                   *
*                              *
*  ]1 = BYTE TO GET BIT (1B)   *
*                              *
* CYCLES: 15                   *
* BYTES : 17                   *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
GBIT     MAC
         LDA   ]1         ; {3B4C} BYTE TO CHECK {NZ}
         AND   ]2         ; {3B4C} MASK BIT # REQUESTED VIA HOOK {NZ}
         BEQ   ]ZERO      ; {2B2C} IF IT'S A MATCH, THEN 0
         LDA   #1         ; {2B2C} OTHERWISE, BIT IS 1 {NZ}
         JMP   ]EXIT      ; {3B3C} GOTO EXIT
]ZERO    LDA   #0         ; {2B2C} BIT IS 0 {NZ}
]EXIT    <<<
*

THE NEGA MACRO

SUMMARY

Condition Value
Name NEGA
Type Macro
Author Nathan Riggs
Last Revision 06-JUN-2019
Assembler Merlin 8 Pro
OS Apple DOS 3.3
Purpose get negative value of .A register
Input .A = Value to negate
Output none
Dependencies none
Flags Destroyed NZ
Cycles 15
Bytes 17
Notes none
See Also none

DETAILS

The NEGA macro simply gets the negative value of the current byte in the .A register. This uses two's-complement for negative numbers, and of course only works on a single byte.

LISTING 1.21: The NEGA Macro Source

*
*``````````````````````````````*
* NEGA                         *
*                              *
* GET THE NEGATIVE EQUIVALENT  *
* OF A BYTE IN .A REGISTER.    *
*                              *
* CYCLES: 7                    *
* SIZE: 5 BYTES                *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
NEGA     MAC
         EOR   #$FF       ; {2C2B}
         CLC              ; {2C1B}
         ADC   #1         ; {3C2B}
         <<<
*

PART II: ALIASES

Disk I also contains alias files that hold macros that emulate parts of the instruction sets of different processors on the 6502, when possible. Obviously, the cases for which this can happen are limited: the 6502 has fewer registers than most other processors, fewer capabilities in some cases which is one reason why it was cheap to use, and limited addressing capacities. Thus far, the following processors have some alias macros that emulate the same behavior on the 6502:

  • 8080/8086 family (up to 286)
  • z80 Family of Processors

It is advisable to use only one alias family at a time, as some instruction sets share instruction names. Note that instructions related to mathematics are rarely used here, as these are mainly part of the math library already. Only basic instructions like branching are included in the alias files, and thus the source code for each is rather simple. As such, we won't be LISTING the source here, but providing tables of the instruction set macros with their cycles, bytes used and purpose. If a particular instruction becomes complicated enough to merit explanation, we will list it separately from the rest of the instruction set table.

8080 Instruction Set Macro Substitutions

The following instruction set macro replacements for the 8080/8086 line of processors are largely branching instructions, although two others stand out: ANC and SNC. These stand for Add No Carry and Subtract No Carry, respectively, and will be removed from here and added to the math library once revision once again reaches that disk.

Instruction / Macro Purpose Bytes Cycles
CALL MIMICS JSR OPERATION 3 6
RET MIMICS RTS OPERATION 1 6
JA JMP IF .A > CMP 9 14
JAE JMP IF .A >= CMP 4 8
JB JMP IF < CMP 4 8
JBE JMP IF <= CMP 6 11
JC JMP IF C = 1 4 8
JE JMP IF EQUAL 4 8
JG JMP IF .A > CMP 9 11
JGE JMP IF .A >= CMP 4 8
JL JMP IF .A < CMP 4 8
JLE JMP IF .A <= CMP 6 11
JNC JMP IF C = 0 4 8
JNE JMP IF NOT EQUAL 4 8
JZ JMP IF Z = 1 4 8
JNZ JMP IF Z = 0 4 8
JS JMP IF SIGNED 4 8
JNS JMP IF NOT SIGNED 4 8
JO JMP IF OVERFLOW = 1 4 8
JNO JMP IF OVERFLOW = 0 4 8
ANC ADD NO CARRY ADD in 8080 8 12
SNC SUBTRACT NO CARRY SUB in 8080 8 12
PUSHA PUSH ALL REGISTERS 14 30
PULLA PULL ALL REGISTERS 24 19
POPA PULL ALL REGISTERS 24 19

Z80 Family Instruction Set Macro Substitutions

Currently, there are few instruction set macro substitutions for the z80 processor because they operate in very different ways. However, they is some overlap, and as much overlap as possible will be provided by these aliases.

Instruction / Macro Purpose Bytes Cycles
CPL invert bits in .A 2 2
JP JMP equivalent 1 5
LD move value from src to dest non-operational ? ?
POP PLA equivalent 1 4
SCF SEC equivalent 1 2

Part III: REQUIRED LIBRARY DEMO FILE

At the the end of each collection, a demo is provided that shows the macros being used as they should be (or could be). These demos are not meant to be exhaustive, but are meant to merely illustrate how the macro is called, what parameters it might require, and so on. By and large, the demos are all kept fairly simple due to the fact that 1) they cannot interact with the macros and subroutines provided by other libraries, and 2) they are meant for beginners to see how a macro works and not much else. More complicated demos are planned for future disks in the package, and some of them are already finished (though in need of updating to the current version).

The demo file listed here is the same DEMO.REQUIRED file on the disk. Note that most of the heavier descriptions of the macros are done in commenting in order to cut down on the demo file size in bytes, but _PRN and _WAIT are particularly used in demo files to give some context when the demo itself is executed. Note also that demo files, like all other files, have unique headings before the code proper begins.

DEMO.REQUIRED

LISTING 1.22: DEMO.REQUIRED

*
*``````````````````````````````*
* DEMO.REQUIRED                *
*                              *
* THIS IS A DEMO OF THE CORE   *
* SUBROUTINES THAT THE WHOLE   *
* LIBRARY USES TO FUNCTION.    *
* MOST OF THESE ARE NOT USED   *
* BY THE PROGRAMMER, BUT ARE   *
* INTERNAL TO THE WORKINGS OF  *
* OTHER SUBROUTINES.           *
*                              *
* AUTHOR:    NATHAN RIGGS      *
* CONTACT:   NATHAN.RIGGS@     *
*            OUTLOOK.COM       *
*                              *
* DATE:      06-JUN-2021       *
* ASSEMBLER: MERLIN 8 PRO      *
* OS:        DOS 3.3           *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
** ASSEMBLER DIRECTIVES
*
         CYC   AVE
         EXP   OFF
         TR    ON
         DSK   DEMO.REQUIRED
         OBJ   $BFE0
         ORG   $6000
*
*``````````````````````````````*
*  TOP INCLUDES (HOOKS,MACROS) *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
         PUT   MIN.HEAD.REQUIRED.ASM
         USE   MIN.MAC.REQUIRED.ASM
*
]COUT    EQU   $FDF0
]HOME    EQU   $FC58
*
         JSR   ]HOME
*
*``````````````````````````````*
*      PROGRAM MAIN BODY       *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
*** SINCE THIS GROUP OF MACROS ARE PRIMARILY USED
*** INTERNALLY FOR THE LIBRARIES, DEMONSTRATIONS OF
*** THEIR USAGE WILL NOT BE GIVEN HERE. HOWEVER, A
*** QUICK LOOK AT ALMOST ANY LIBRARY'S MACRO FILE
*** WILL SHOW QUITE EASILY HOW THESE MACROS ARE USED.
*
* THE _AXLIT MACRO - .AX LITERAL
* THE _AXSTR MACRO - .AX STRING
* THE _ISLIT MACRO - IS LITERAL
* THE _ISSTR MACRO - IS STRING
* THE _MLIT MACRO  - MEMORY LITERAL
* THE _MSTR MACRO  - MEMORY STRING
*
*``````````````````````````````*
* _PRN, _WAIT AND BEEP         *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
*** THE _PRN, _WAIT AND BEEP MACROS ARE MOSTLY
*** AVAILABLE FOR DEBUGGING PURPOSES, AND ARE NOT
*** MEANT TO BE USED IN ACTUAL USER PROGRAMS
*** BEYOND THAT CAPACITY.
*
** THE _PRN MACRO   - PRINT
*
** THE _PRN MACRO SIMPLY PRINTS A LITERAL STRING
** TO THE SCREEN, AND HAS VERY LIMITED USE. WHEN
** OUTPUT IS MORE IMPORTANT THAN AN ERROR MESSAGE
** OR THE LIKE, USE THE MACROS PROVIDED BY THE
** STDIO LIBRARY.
*
         _PRN  "THIS IS A TEST.",8D8D
         _WAIT
*
* THE _WAIT MACRO  - WAIT
*
** LIKEWISE, THE _WAIT MACRO IS FOR USES OF
** CONVENIENCE ONLY, AND THE STDIO LIBRARY
** SHOULD BE USED INSTEAD WHENEVER POSSIBLE.
** NONETHELESS, THE _WAIT MACRO SIMPLY WAITS
** FOR A KEYPRESS BEFORE EXECUTION CONTINUES.
*
         _PRN  "THE WAIT MACRO",8D
         _PRN  "==============",8D8D
         _WAIT
*
* THE BEEP MACRO   - BEEP
*
** UNSURPRISINGLY, THE BEEP MACRO SENDS A STANDARD
** NUMBER OF TICKS TO THE SPEAKER TO PRODUCE A BEEP
** THAT CAN BE A LENGTH BETWEEN 1 AND 255.
*
         _PRN  "BEEEEEEEEEEEEEEP",8D8D
         BEEP  #255
         _WAIT
*
*``````````````````````````````*
* MEMORY MANIPULATION MACROS   *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
* THE DUMP MACRO   - MEMORY DUMP
*
** THE DUMP MACRO HAS ACTUALLY ALREADY BEEN
** USED TO SHOW THE CONTENTS RETURNED BY SOME
** OF THE 65C02 EMULATION MACROS. THE MACRO
** SIMPLY DUMPS A BLOCK OF MEMORY SPECIFIED BY
** THE PARAMETERS PASSED. THEREFORE:
*
         _PRN  "THE DUMP MACRO",8D
         _PRN  "==============",8D8D
         LDA   #$BB
         STA   $300
         LDA   #$AA
         STA   $301
         _PRN  "VALUES IN $301-$302:"
         DUMP  #$300;#2
         _WAIT
         _PRN  " ",8D8D
*
*``````````````````````````````*
* THE WEIRDOS: CLRHI, GBIT AND *
* NEGA MACROS.                 *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
*** THESE MACROS CAN BE HIGHLY USEFUL,
*** BUT SO FAR DO NOT FIT WELL WITH ANY
*** OTHER GROUPING OF MACROS.
*
* THE CLRHI MACRO  - CLEAR HIGH NIBBLE
*
** THE CLRHI MACRO CLEARS THE HIGH NIBBLE IN .A
** BYTE TO %0000. THIS HAS VARIOUS USES THAT NEED
** NOT BE GONE OVER HERE.
*
         _PRN  "THE CLRHI MACRO",8D
         _PRN  "===============",8D8D
         LDA   #$FF
         CLRHI
         STA   $300
         DUMP  #$300;#1
         _PRN  "^^ THE HIGH NIBBLE WAS CLEARED"
         _PRN  " ",8D8D
         _WAIT
*
* THE GBIT MACRO   - GET BIT VALUE
*
** THE GBIT MACRO RETURNS THE BIT VALUE
** OF THE GIVEN POSITION IN THE PARAMETER.
*
         _PRN  "THE GBIT MACRO",8D
         _PRN  "==============",8D8D
         GBIT  #0;%00100101
         STA   $300
         DUMP  #$300;#1
         _WAIT
*
** THE NEGA MACRO
*
** THE NEGA MACRO SIMPLY RETURNS IN .A THE NEGATIVE
** EQUIVALENT OF THE VALUE ALREADY IN .A.
*
         LDA   #$80
         NEGA
         STA   $300
         DUMP  #$300;#1
         _WAIT
*
         JSR   ]HOME
*
*``````````````````````````````*
* 8080 ALIAS MACROS            *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
*** THE 8080 ALIAS MACROS ARE APPROXIMATIONS
*** OF SOME OF THE INSTRUCTIONS IN THE 8080
*** INSTRUCTION SET, FOR USE BY THOSE WHO ARE
*** USED TO PROGRAMMING IN 8080 ASSEMBLY. NOTE
*** AGAIN THE WORD **APPROXIMATION**, AS I AM
*** NOT AN 8080 PROGRAMMER.
*
*** ALSO NOTE THAT YOU CAN ONLY USE ONE SET OF
*** ALIAS FILES AT A TIME; OTHERWISE, MACROS
*** WITH THE SAME NAME IN EACH ALIAS FILE WILL
*** CAUSE AN ASSEMBLY ERROR. THIS IS MOST NOTABLE
*** WITH INSTRUCTIONS LIKE 'RET' OR 'CALL."
*
*** SINCE ALL OF THESE MACROS ARE FAIRLY SIMPLE,
*** EXAMPLES ARE NOT GIVEN HERE. A FEW MACROS MAY
*** NEED EXAMPLES IN THE FUTURE, BUT THESE MOSTLY
*** WORK THE WAY YOU MIGHT EXPECT.
*
*
* CALL  - MIMICS JSR INSTRUCTION
* RET   - MIMICS RTS INSTRUCTION
* JA    - JMP IF .A > CMP RESULT
* JAE   - JMP IF .A => CMP RESULT
* JB    - JMP IF .A < CMP RESULT
* JBE   - JMP IF .A =< CMP RESULT
* JC    - JMP IF C = 1
* JE    - JMP IF BEQ
* JG    - JMP IF .A > CMP RESULT
* JGE   - JMP IF .A >= CMP RESULT
* JL    - JMP IF .A < CMP RESULT
* JLE   - JMP IF .A <= CMP RESULT
* JNC   - JMP IF C = 0
* JZ    - JMP IF Z = 0
* JNZ   - JMP IF Z = 1
* JS    - JMP IF SIGNED
* JNS   - JMP IF NOT SIGNED
* JO    - JMP IF V = 1
* JNO   - JMP IF V = 0
* ANC   - ADD NO CARRY
* SNC   - SUBTRACT NO CARRY
* PUSHA - PUSH ALL REGISTERS
* PULLA - PULL ALL REGISTERS
* POPA  - PULL ALL REGISTERS
*
*``````````````````````````````*
* Z80 ALIAS MACROS             *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
*** THESE MACROS ARE APPROXIMATIONS OF
*** SOME INSTRUCTIONS IN THE Z80 SET, AND
*** ARE MEANT TO AID THE PROGRAMMER WHO IS
*** USED TO A Z80 CPU. CURRENTLY, ALL OF THESE
*** EXAMPLES ARE COMMENTED OUT DUE TO THE FACT
*** THAT SOME OF THE MACRO NAMES CONFLICT WITH
*** THE 8080 ALIAS SET. TO SEE THESE IN ACTION,
*** YOU MUST FIRST COMMENT OUT THE 8080 EXAMPLES
*** AND THEN COMMENT OUT THE 8080 ALIAS FILE
*** INCLUDE AT THE END, WHILE ALSO UNCOMMENTING
*** THE Z80 ALIAS FILE INCLUSION.
*
* CPL   - INVERTS BITS IN .A
* JP    - JMP EQUIVALENT
* LD    - MOV VALUE FROM SRC TO DEST
* POP   - PULL FROM STACK
* SCF   - C = 1
*
         JMP   REENTRY
*
*``````````````````````````````*
*        BOTTOM INCLUDES       *
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
*
** BOTTOM INCLUDES
*
         PUT   MIN.LIB.REQUIRED.ASM
         USE   MIN.MAC.ALIAS.8080.ASM
         USE   MIN.MAC.ALIAS.Z80.ASM
*

Return to Table of Contents Next: Detailed Reference: Disk 2: Standard Input and Output