mirror of
https://github.com/nathanriggs/AppleIIAsm-Collection.git
synced 2024-12-12 16:29:26 +00:00
4554eaf2ae
- added cycle and byte count comments - additionally added status flag clobbering comments - wrote beginning of final documentation form in markdown for disk 1: REQUIRED - consolidated macros and subroutines on disk 1 - created alias macros for 65C02, 8080, and Z80 architectures - entirely rewrote and expanded documentation for disk 1 - rewrote disk 1 demo file to use fewer bytes by using comments instead of _PRN statements - implemented a software architeture that will span across all libraries in the collection - added .ASM extension to text sources to help with file attribution on contemporary systems - slightly modified the naming convention for EXEC files - fixed minor errors
3860 lines
140 KiB
Markdown
3860 lines
140 KiB
Markdown
# Disk 1 : Required Library and Aliases
|
|
|
|
- [Part I: The Required Library](#part-i-required-library)
|
|
- [Required Components](#required-components)
|
|
- [Required Header File](#required-header-file)
|
|
- [Next Up: Required Macros](#next-up-required-macros)
|
|
- [Internal Parameter Macros](#internal-parameter-macros)
|
|
- [The `_AXLIT` Macro](#the-axlit-macro)
|
|
- [The `_AXSTR` Macro](#the-axstr-macro)
|
|
- [The `_ISLIT` Macro](#the-islit-macro)
|
|
- [The `_ISSTR` Macro ](#the-isstr-macro)
|
|
- [The `_MLIT` Macro](#the-mlit-macro)
|
|
- [The `_MSTR` Macro](#the-mstr-macro)
|
|
- [Subroutine Shortcuts](#subroutine-shortcuts)
|
|
- [The `_PRN` Macro](#the-prn-macro)
|
|
- [The `__P` Subroutine](#the-p-subroutine)
|
|
- [The `_WAIT` Macro](#the-wait-macro)
|
|
- [The `BEEP` Macro](#the-beep-macro)
|
|
- [Long Branches](#long-branches)
|
|
- [The `BCCL` Macro](#the-bccl-macro)
|
|
- [The `BCSL` Macro](#the-bcsl-macro)
|
|
- [The `BEQL` Macro](#the-beql-macro)
|
|
- [The `BMIL` Macro](#the-bmil-macro)
|
|
- [The `BNEL` Macro](#the-bnel-macro)
|
|
- [The `BPLL` Macro](#the-bpll-macro)
|
|
- [The `BVCL` Macro](#the-bvcl-macro)
|
|
- [The `BVSL` Macro](#the-bvsl-macro)
|
|
- [Fake 65C02 Instructions](#fake-65c02-instructions)
|
|
- [The `CBRA` Macro](#the-cbra-macro)
|
|
- [The `CPHX` Macro](#the-cphx-macro)
|
|
- [The `CPHY` Macro](#the-cphy-macro)
|
|
- [The `CPLX` Macro](#the-cplx-macro)
|
|
- [The `CPLY` Macro](#the-cply-macro)
|
|
- [The `CTXY` Macro](#the-ctxy-macro)
|
|
- [The `CTYX` Macro](#the-ctyx-macro)
|
|
- [Miscellaneous Useful Macros](#miscellaneous-useful-macros)
|
|
- [The `CLRHI` Macro](#the-clrhi-macro)
|
|
- [The `DELAY` Macro](#the-delay-macro)
|
|
- [The `DELAYMS` Subroutine](#the-delayms-subroutine)
|
|
- [The `DUMP` Macro](#the-dump-macro)
|
|
- [The `__DUMP` Subroutine](#the-dump-subroutine)
|
|
- [The `ERRH` Macro](#the-errh-macro)
|
|
- [The `__ERRH` Subroutine](#the-errh-subroutine)
|
|
- [The `GBIT` Macro](#the-gbit-macro)
|
|
- [The `GRET` Macro](#the-gret-macro)
|
|
- [Memory Macros and Subroutines](#memory-macros-and-subroutines)
|
|
- [The `PEEK` Macro](#the-peek-macro)
|
|
- [The `POKE` Macro](#the-poke-macro)
|
|
- [The `MFILL` Macro](#the-mfill-macro)
|
|
- [The `MEMFILL` Subroutine](#the-memfill-subroutine)
|
|
- [The `MOVB` Macro](#the-movb-macro)
|
|
- [The `MEMMOVE` Subroutine](#the-memmove-subroutine)
|
|
- [The `MSWAP` Macro](#the-mswap-macro)
|
|
- [The `MEMSWAP` Subroutine](#the-memswap-subroutine)
|
|
- [Part II: Aliases](#part-ii-aliases)
|
|
- [8080 Instruction Set Macro Substitutions](#8080-instruction-set-macro-substitutions)
|
|
- [Z80 Family Instruction Set Macro Substitutions](#z80-family-instruction-set-macro-substitutions)
|
|
- [Part III: Required Library Demo File](#part-iii-required-library-demo-file)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## PART I: REQUIRED LIBRARY
|
|
|
|
The first disk in the AppleIIAsm Collection 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 libraries as easy for beginners to use as possible while also allowing for substantial changes to the architecture to allow 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, however, 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 library; 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 library, 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 780 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 file are used by their libraries 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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Initialize system for use by rest of libraries |
|
|
| Dependencies | none |
|
|
| Bytes | 311 |
|
|
| Notes | Required for nearly every macro or subroutine |
|
|
|
|
---
|
|
|
|
*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`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* 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: 29-NOV-2019 *
|
|
* ASSEMBLER: Merlin Pro 8 *
|
|
* OS: DOS 3.3 *
|
|
* *
|
|
* BYTES: 311 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
```
|
|
|
|
|
|
|
|
From the beginning, the required header file provides space for a small jump table that begins with a jump to the end of the header file, but allows for an additional 16 more addresses for use in the table. Note that you can of course increase or decrease the size of the jump table, but be sure to do so in terms of words (2 bytes), and never get rid of the initial jump to the end of the header file--otherwise, date will be executed as code and an error will occur. Then follows 20 bytes for variable definitions by the rest of the library, titled `VARTAB`. While this can also be increased if the end user also wishes to use space here (though that can be haphazard), reducing the number of bytes here will break certain, more complicated subroutines and thus should not be lowered.
|
|
|
|
What then follows is 256 bytes dedicated to values returned by subroutines. The first byte, `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`.
|
|
|
|
The rest of the header defines certain hooks and variables that the rest of the libraries depend on. For instance, `ADDR1` through `ADDR4` are often used for indirect addressing modes, and are helpful in the sense that they are all sequential: a high nibble precedes a low nibble, for instance, in **$06** and **$07**. Following the addressing indices, scratchpad locations on the zero page are identified as `SCRATCH` and `SCRATCH2`, though `SCRATCH` is most often used for backing up **.Y** values before and after a macro and thus should rarely be used beyond that capacity. The last variable, `RETVAL`, is used on the rare occasion when usage of the stack requires a return location to be backed up elsewhere.
|
|
|
|
The hooks `RESULT` and `RESULT2` are rarely used, though this may change in the future. The following word and byte parameter passing hooks, however, are likely the most used parts of the zero page in this collection: `WPAR1`, `WPAR2`,`WPAR3` and `WPAR4` are all used primarily for passing word-length values (16-bits) to subroutines, and have sequential memory locations, whereas `BPAR1`, `BPAR2`, `BPAR3` and `BPAR4` are used for passing single bytes (these do not have sequential free memory locations, and are thus unsuitable for anything by bytes) .
|
|
|
|
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 follow.
|
|
|
|
`Listing 1.1: HEAD.REQUIRED CODE`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* 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: 11-DEC-2019 *
|
|
* ASSEMBLER: MERLIN 8 PRO *
|
|
* OS: DOS 3.3 *
|
|
* *
|
|
* BYTES: 311 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
* VARIABLE DECLARATIONS ********
|
|
*
|
|
** JUMP TABLE SETUP. THIS IS FOR LOADING
|
|
** SUBROUTINES INTO MEMORY FOR ACCESS BY
|
|
** EXTERNAL EXECUTIONS. NOTE THAT THIS
|
|
** SHOULD ALWAYS START AT SECOND BYTE OF
|
|
** CODE IN THE PROGRAM SO THAT ITS
|
|
** LOCATION IN MEMORY IS EASILY KNOWN.
|
|
*
|
|
JUMPTBL JMP MAIN_START ; ** ALWAYS ** START WITH
|
|
; JUMP TO MAIN_START
|
|
DS 32 ; 16 MORE ENTRIES
|
|
*
|
|
** 20 BYTES FOR VARIABLES
|
|
*
|
|
VARTAB DS 20
|
|
*
|
|
** 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
|
|
*
|
|
** ADDRESS STORAGE LOCATIONS FOR
|
|
** INDIRECT ADDRESSING.
|
|
*
|
|
ADDR1 EQU $06 ; AND $07
|
|
ADDR2 EQU $08 ; AND $09
|
|
ADDR3 EQU $EB ; AND $EC
|
|
ADDR4 EQU $ED ; AND $EE
|
|
*
|
|
** SCRATCHPAD ZERO PAGE LOCATIONS AND
|
|
** DEDICATED ZERO PAGE ADDRESS TO HOLD
|
|
** A RETURN ADDRESS PASSED VIA THE STACK
|
|
*
|
|
SCRATCH EQU $19 ; USED TO BACKUP .Y
|
|
SCRATCH2 EQU $1E
|
|
RETADR EQU $FE ; AND $FF
|
|
*
|
|
** ZERO PAGE ADDRESSES DEDICATED TO PASSING
|
|
** BACK RESULTS WHEN THERE ARE MORE THAN
|
|
** THREE BYTES BEING PASSED (AXY) AND THE
|
|
** USE OF THE STACK IS IMPRACTICAL OR TOO SLOW
|
|
*
|
|
RESULT EQU $FA
|
|
RESULT2 EQU $FC
|
|
*
|
|
** WORD AND BYTE PARAMETER SPACE USED
|
|
** BY APPLEIIASM MACROS
|
|
*
|
|
WPAR1 EQU $FA
|
|
WPAR2 EQU $FC
|
|
WPAR3 EQU $FE
|
|
BPAR1 EQU $EF
|
|
BPAR2 EQU $E3
|
|
BPAR3 EQU $1E
|
|
BPAR4 EQU $19
|
|
*
|
|
** VARIOUS HOOKS USED BY ALL ROUTINES
|
|
*
|
|
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
|
|
*
|
|
|
|
MAIN_START
|
|
```
|
|
|
|
|
|
|
|
## 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 libraries in AppleIIAsm, 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.2: MAC.REQUIRED File Heading`
|
|
```ASM
|
|
*
|
|
*``````````````````````````````*
|
|
* 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: 11-DEC-2019 *
|
|
* ASSEMBLER: Merlin Pro 8 *
|
|
* OS: DOS 3.3 *
|
|
* *
|
|
* SUBROUTINE FILES NEEDED *
|
|
* *
|
|
* LIB.REQUIRED *
|
|
* *
|
|
* 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 *
|
|
* BCCL : LONG ADDR BCC *
|
|
* BCSL : LONG ADDR BCS *
|
|
* BEEP : BEEP FOR SOME TIME *
|
|
* BEQL : LONG ADDR BEQ *
|
|
* BMIL : LONG ADDR BMI *
|
|
* BNEL : LONG ADDR BNE *
|
|
* BPLL : LONG ADDR BPL *
|
|
* BVCL : LONG ADDR BVC *
|
|
* BVSL : LONG ADDR BVS *
|
|
* CBRA : 65C02 BRANCH ALWAYS *
|
|
* CLRLO : CLEAR LOW NIBBLE *
|
|
* CLRHI : CLEAR HIGH NIBBLE *
|
|
* CPHX : FAKE 65C02 PUSH X *
|
|
* CPHY : FAKE 65C02 PUSH Y *
|
|
* CPLX : FAKE 65C02 PULL X *
|
|
* CPLY : FAKE 65C02 PULL Y *
|
|
* CSTZ : FAKE 65C02 STORE Z *
|
|
* CTXY : TRANSFER .X TO .Y *
|
|
* CTYX : TRANSFER .Y TO .X *
|
|
* DELAY : DELAY SET MLSECONDS *
|
|
* DUMP : DUMP MEMORY *
|
|
* ERRH : SET ERROR ROUTINE *
|
|
* GRET : GET RETURN *
|
|
* MFILL : FILL MEMORY BLOCKS *
|
|
* MOV : MOVE A PIECE OF MEM *
|
|
* MOVB : MOVE A BLOCK OF MEM *
|
|
* MSWAP : SWAP BLOCKS OF MEM *
|
|
* PEEK : LOAD .A FROM ADDR *
|
|
* POKE : STORE ADDR VAL IN A *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
** LIBRARY-SPECIFIC VARIABLES
|
|
*
|
|
]RIGHT DS 1
|
|
]LEFT DS 1
|
|
]LENGTH DS 1
|
|
]A DS 1 ; REGISTER .A BACKUP
|
|
]X DS 1 ; REGISTER .X BACKUP
|
|
]Y DS 1 ; REGISTER .Y BACKUP
|
|
]C DS 1 ; CARRY FLAG BACKUP
|
|
]Z DS 1 ; ZERO FLAG BACKUP
|
|
]N DS 1 ; NEGATIVE FLAG BACKUP
|
|
]O DS 1 ; OVERFLOW FLAG BACKUP
|
|
]HEXTAB ASC "0123456789ABCDEF"
|
|
*
|
|
** LIBRARY-SPECIFIC HOOKS
|
|
*
|
|
]COUT EQU $FDF0 ; SCREEN OUTPUT ROUTINE
|
|
]KYBD EQU $C000 ; KEYBOARD INPUT
|
|
]STROBE EQU $C010 ; KEYBOARD STROBE
|
|
*
|
|
```
|
|
|
|
|
|
|
|
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: `WPAR1`, `BPAR1`, 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 | 29-NOV-2019 |
|
|
| Assembler | Merlin Pro 8 |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | parse macro parameters for subroutine passing |
|
|
| Input | ]1 = memory address byte (literal or not) |
|
|
| Output | places appropriate data in **.A** and **.X** based on whether parameter is literal |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 8 |
|
|
| Bytes | 6 (every use) |
|
|
| Notes | note that literal values are *never* actually used as literal values, in any macro or subroutine. |
|
|
|
|
---
|
|
|
|
*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. But first, just like with everything else, we should start with the heading.
|
|
|
|
`Listing 1.3: _AXLIT Macro Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _AXLIT *
|
|
* *
|
|
* CHECKS IF PARAMETER IS A *
|
|
* LITERAL OR NOT, AND SETS THE *
|
|
* LOW AND HIGH IN .A AND .X. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* *
|
|
* 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 every time it is called. Note 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. Note that I tend to take the pessimists side, as you'll soon see, and would rather add possible cycles or bytes rather than 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.4: _AXLIT Macro Code`
|
|
|
|
|
|
```asm
|
|
_AXLIT MAC ; CHECK IF LITERAL
|
|
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
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
|
|
|
|
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. Your best bet would be to start with the first few chapters of the first book in this technical documentation series, _A New Users' Guide to Merlin 8 Pro_, to familiarize yourself with the interface, then move to the next book,_From AppleSoft to AppleChop to ASM_ to move from a BASIC mindset (or Pascal, etc.) to an Assembly mindset. Of course, you are more than welcome to use other resources instead.
|
|
|
|
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 | 29-NOV-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 |
|
|
| 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 <br/>the _AXLIT macro for more processing. |
|
|
|
|
---
|
|
|
|
*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.5: _AXSTR Macro Heading`
|
|
|
|
```ASM
|
|
*
|
|
*``````````````````````````````*
|
|
* _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.6: _AXSTR Macro Code`
|
|
|
|
|
|
```asm
|
|
_AXSTR MAC ; CHECK IF STRING
|
|
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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 14 |
|
|
| Bytes | 8 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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.7: _ISLIT Macro Heading and Code`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _ISLIT *
|
|
* *
|
|
* CHECKS IF THE PARAMETER IS *
|
|
* A LITERAL OR NOT, THEN *
|
|
* PUSHES THE LO AND HI AS *
|
|
* NEEDED. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* *
|
|
* CYCLES: 14 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
_ISLIT MAC ; CHECK IF LITERAL
|
|
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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | `_ISLIT` |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 13 or 8 |
|
|
| Bytes | 10+ or 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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.8: _ISSTR Macro Heading and Code`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _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 BYTE *
|
|
* OR STRING *
|
|
* *
|
|
* CYCLES: 13 -OR- 8 *
|
|
* BYTES: 10+ -OR- 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
_ISSTR MAC ; CHECK IF STRING
|
|
IF "=]1 ;...... IF ]1 IS A STRING
|
|
JMP __STRCONT ; {3C3B} KIP 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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 16 |
|
|
| Bytes | 12 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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 hooks `WPAR1` or `BPAR` 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.9: _MLIT Macro Heading and Code`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _MLIT *
|
|
* *
|
|
* CHECKS IF PARAMETER IS A *
|
|
* LITERAL OR NOT, AND SETS THE *
|
|
* LO AND HI IN THE SPECIFIED *
|
|
* MEMORY ADDRESS. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* ]2 = ZERO PAGE ADDRESS *
|
|
* *
|
|
* CYCLES: 16 *
|
|
* BYTES: 12 (EITHER WAY) *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
_MLIT MAC ; CHECK IF LITERAL
|
|
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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | `_ISLIT` |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 15 or 8 |
|
|
| Bytes | 14+ or 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _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 BYTE *
|
|
* OR STRING *
|
|
* ]2 = ZERO PAGE ADDRESS *
|
|
* *
|
|
* CYCLES: 15 -OR- 8 *
|
|
* BYTES: 14 +STRING BYTES OR 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
_MSTR MAC ; CHECK IF STRING
|
|
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
|
|
_ISLIT ]1 ; {8C6B} CHECK IF LITERAL
|
|
FIN ; END IF
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
---
|
|
|
|
|
|
|
|
## 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 library. This is especially useful when debugging and writing demo programs for libraries 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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 161+ |
|
|
| Bytes | 9+ |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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 library 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`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _PRN *
|
|
* *
|
|
* PRINT A STRING OR ADDRESS. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* 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`
|
|
|
|
```asm
|
|
_PRN MAC ; PRINT STRING
|
|
STY SCRATCH ; {3C2B} PRESERVE .Y
|
|
JSR __P ; {161C3B} PRINT THE STRING {NZCV}
|
|
ASC ]1 ; {0C1B+} HOLD STRING HERE
|
|
HEX 00 ; {0C1B} KILL STRING PRINT
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | `ASC` string trailing call to subroutine |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 155+ |
|
|
| Bytes | 35 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`Listing 1.13: __P Subroutine Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* __P: (NATHAN RIGGS) *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* ASC STRING FOLLOWING CALL *
|
|
* TERMINATED WITH A 00 BYTE *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* CONTENTS OF STRING. *
|
|
* *
|
|
* DESTROYS: NZCIDV *
|
|
* ^^^ ^ *
|
|
* *
|
|
* CYCLES: 72+(155+ WITH COUT) *
|
|
* 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) .**.Y** 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 Code`
|
|
|
|
```asm
|
|
__P
|
|
PLA ; {4C1B} PULL RETURN LOBYTE {NZ}
|
|
STA ADDR1 ; {3C2B} STORE TO ZERO PAGE
|
|
PLA ; {4C1B} PULL RETURN HIBYTE {NZ}
|
|
STA ADDR1+1 ; {3C2B} STORE TO ZERO PAGE
|
|
LDY #1 ; {2C2B} SET OFFSET TO PLUS ONE
|
|
:LP LDA (ADDR1),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 ADDR1 ; {3C2B} ADD OFFSET TO RETURN ADDR {NZCV}
|
|
STA ADDR1 ; {4C2B} STORE TO RETURN ADDRESS LOBYTE
|
|
LDA ADDR1+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 ADDR1 ; {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 | 29-NOV-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | ]1 = memory address byte |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 14+ |
|
|
| Bytes | 10 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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 Code`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* _WAIT *
|
|
* *
|
|
* WAIT FOR A KEYPRESS. *
|
|
* *
|
|
* NO PARAMETERS *
|
|
* *
|
|
* CYCLES: 14+ *
|
|
* BYTES : 10 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
_WAIT MAC ; WAIT FOR KEYPRESS
|
|
]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
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
|
|
### THE BEEP MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BEEP` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Beep Alert sound |
|
|
| Input | ]1 = length of beep |
|
|
| Output | |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 108+ |
|
|
| Bytes | 15 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BEEP *
|
|
* *
|
|
* RING THE STANDARD BELL. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = NUMBER OF RINGS *
|
|
* *
|
|
* CYCLES: 108+ *
|
|
* BYTES: 15 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BEEP MAC ; ROUTINE BEEP
|
|
STY SCRATCH ; {3C2B} BACKUP .Y REGISTER
|
|
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
|
|
LDY SCRATCH ; {3C2B} OTHERWISE, RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
---
|
|
|
|
|
|
|
|
## LONG BRANCHES
|
|
|
|
Long branches are branching macros that work pretty much exactly like the branching mechanism they represent, except they can jump to an address beyond a single page away. This is especially helpful in prototyping, as normal branching routines often start failing once you begin to add content between the branching instruction and the address to branch to. However, without quite a bit of reworking, using these macros also makes your code non-relocatable, as specific addresses are `JMP'd` to that may no longer exist as first envisioned.
|
|
|
|
### THE BCCL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BCCL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BCC to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BCC won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The BCCL macro simply performs a BCC instruction and, if the condition is true, then branches to a nearby instruction that performs a JMP to the actual address you want the the BCC to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BCC` instruction stands for _Branch Carry Clear_, and branches execution of a program to a specified address when the carry flag is set to 0. `BCC` is also used to test whether a `CMP` comparison yields a result in which the **.A** register holds a value less than the comparison value. Thus, `BCCL` simply does the same, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.17: BCCL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BCCL *
|
|
* *
|
|
* BCC TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = ADDR TO BRANCH TO *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BCCL MAC ; LONG BCC
|
|
BCC ]TOBCC ; {4C2B} IF CARRY CLEAR, BRANCH
|
|
]NOBCC JMP ]EXIT ; {3C3B} OTHER, RETURN
|
|
]TOBCC JMP ]1 ; {3C3B} JMP TO BCC BRANCH LOC
|
|
]EXIT ; CARRY WASN'T CLEAR
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE BCSL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BCSL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BCS to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BCC won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The BCSL macro simply performs a BCS instruction and, if the condition is true, then branches to a nearby instruction that performs a JMP to the actual address you want the the BCS to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BCS` instruction stands for _Branch Carry SET_, and branches execution of a program to a specified address when the carry flag is set to 1. `BCS` is also used to test whether a `CMP` comparison yields a result in which the **.A** register holds a value greater than or equal to the comparison value. Thus, `BCSL` simply does the same, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.18: BCSL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BCSL *
|
|
* *
|
|
* BCS TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BCSL MAC ; LONG BCS
|
|
BCS ]TOBCS ; {4C2B} IF CARRY SET, THEN BRANCH
|
|
]NOBCS JMP ]EXIT ; {3C3B} OTHERWISE, DON'T BRANCH
|
|
]TOBCS JMP ]1 ; {3C3B} JMP TO BRANCH
|
|
]EXIT ; EXIT LABEL
|
|
<<< ; END MACRO
|
|
```
|
|
### THE BEQL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BEQL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BEQ to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BEQ won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The BEQL macro simply performs a BEQ instruction and, if the condition is true, then branches to a nearby instruction that performs a JMP to the actual address you want the the BEQ to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BEQ` instruction stands for _Branch Equal_, and branches execution of a program to a specified address when the zero flag is set. `BEQ` is also used to test whether a `CMP` comparison yields a result in which the **.A** register holds a value equal to the comparison value. `BEQ` can also be used to test whether many other operations yield a 0 result, and is often used to test whether a loop should end. Regardless, `BEQL` simply does the same as `BEQ`, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.19: BEQL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BEQL *
|
|
* *
|
|
* BEQ TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BEQL MAC ; LONG BEQ
|
|
BEQ ]TOBEQ ; {4C2B} IF CMP EQUAL OR 0, BRANCH
|
|
]NOBEQ JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBEQ JMP ]1 ; {3C3B} JUMP TO BRANCH
|
|
]EXIT ; EXIT LABEL
|
|
<<< ; END MACRO
|
|
```
|
|
### THE BMIL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BMI` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BMI to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BMI won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `BMIL` macro simply performs a `BMI` instruction and, if the condition is true, then branches to a nearby instruction that performs a `JMP` to the actual address you want the the BMI to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BMI` instruction stands for _Branch MINUS_, and branches execution of a program to a specified address when the negative flag is set. `BMI` is obviously used to test if a value is negative, but also doubles as an easy way to test whether the high bit of a value is set. `BMIL` simply does the same as `BMI`, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.20: BMIL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BMIL *
|
|
* *
|
|
* BMI TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BMIL MAC ; LONG BMI
|
|
BMI ]TOBMI ; {4C2B} IF NEGATIVE SET, BRANCH
|
|
]NOBMI JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBMI JMP ]1 ; {3C3B} JUMP TO BRANCH ADDRESS
|
|
]EXIT ; QUIT LABEL
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE BNEL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BNEL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BNE to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BNE won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `BNEL` macro simply performs a` BNE` instruction and, if the condition is true, then branches to a nearby instruction that performs a `JMP` to the actual address you want the the `BNE` to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BNE` instruction stands for _Branch Not Equal_, and branches execution of a program to a specified address when the zero flag is not set. `BNE` is often used at the end of a loop to determine if the decreasing index has reached zero yet, in which case the loop would be exited. `BNEL` simply does the same, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.21: BNEL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BNEL *
|
|
* *
|
|
* BNE TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BNEL MAC ; LONG BNE
|
|
BNE ]TOBNE ; {4C2B} IF CMP != OR !=0, BRANCH
|
|
]NOBNE JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBNE JMP ]1 ; {3C3B} JUMP TO BRANCH ADDR
|
|
]EXIT ; QUITTING LABEL
|
|
<<< ; END MACRO
|
|
```
|
|
### THE BPLL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BPLL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BPL to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BPL won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `BPLL` macro simply performs a `BPL` instruction and, if the condition is true, then branches to a nearby instruction that performs a `JMP` to the actual address you want the the `BPL` to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BPL` instruction stands for _Branch Plus_, and branches execution of a program to a specified address when the negative flag is not set. `BEQ` is also used to test whether a value has its high bit set; if it is not set, the `BPL` returns nothing. `BPLL` simply does the same as `BPL`, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.22: BPLL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BPLL *
|
|
* *
|
|
* BPL TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BPLL MAC ; LONG BPL
|
|
BPL ]TOBPL ; {4C2B} IF NOT NEGATIVE, BRANCH
|
|
]NOBPL JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBPL JMP ]1 ; {3C3B} JUMP TO BRANCH ADDRESS
|
|
]EXIT ; QUIT
|
|
<<< ; END MACRO
|
|
```
|
|
### THE BVCL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BVCL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BVC to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BVC won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `BVCL` macro simply performs a `BVC` instruction and, if the condition is true, then branches to a nearby instruction that performs a `JMP` to the actual address you want the the `BVC` to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BVC` instruction stands for _Branch oVerflow Clear, and branches execution of a program to a specified address when the overflow flag is clear. `BVC` is often used in signed number calculations, but can also be used as a fake `BRA` in relocatable code. Regardless, `BVCL` simply does the same as `BVC`, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.23: BVCL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BVCL *
|
|
* *
|
|
* BVC TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BVCL MAC ; LONG BVC
|
|
BVC ]TOBVC ; {4C2B} IF OVERFLOW CLEAR, BRANCH
|
|
]NOBVC JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBVC JMP ]1 ; {3C3B} JUMP TO BRANCH ADDRESS
|
|
]EXIT ; QUIT
|
|
<<< ; END MACRO
|
|
```
|
|
### THE BVSL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `BVSL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | BVS to far away address |
|
|
| Input | ]1 = address to branch to |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 7 |
|
|
| Bytes | 8 |
|
|
| Notes | Obviously, use this only if BVS won't work |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `BVSL` macro simply performs a `BVS` instruction and, if the condition is true, then branches to a nearby instruction that performs a `JMP` to the actual address you want the the `BVS` to point to. The purpose of this macro, as well as every **Long Branch** macro is clear: to allow for branching further away than normal. However, not that this should only be used if necessary; otherwise, it becomes a waste of both bits and cycles.
|
|
|
|
For those of you who need a reminder: the `BVS` instruction stands for _Branch oVerflow Set_, and branches execution of a program to a specified address when the overflow flag is set. `BVS` is often used in signed arithmetic. `BVSL` simply does the same as `BEQ`, except is capable of branching to an address that is further away.
|
|
|
|
`Listing 1.24: BVSL Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* BVSL *
|
|
* *
|
|
* BVS TO A FAR AWAY ADDRESS *
|
|
* *
|
|
* CYCLES: 7 *
|
|
* BYTES: 8 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
BVSL MAC ; LONG BVS
|
|
BVS ]TOBVS ; {4C2B} OF OVERFLOW SET, BRANCH
|
|
]NOBVS JMP ]EXIT ; {3C3B} OTHERWISE, SKIP BRANCH
|
|
]TOBVS JMP ]1 ; {3C3B} JUMP TO BRANCH ADDRESS
|
|
]EXIT ; QUIT
|
|
<<< ; END MACRO
|
|
```
|
|
---
|
|
|
|
|
|
|
|
## FAKE _65C02_ INSTRUCTIONS
|
|
|
|
These macros try to mimic some of the functionality of the 65c02 instruction set. For the most part, this revolves around pushing and pulling different registers other than **.A**, with one macro dedicated to setting a memory location to a zero value. These are to be used, of course, only in cases when the actual 65c02 instruction set is unavailable.
|
|
|
|
### THE CBRA MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CBRA` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Fake 65C02 Forced Branch |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | none |
|
|
| Cycles | 4 |
|
|
| Bytes | 4 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`CBRA` stands for "65**_C_**02 Branch Always, a function that is not available in the original 6502 instruction set. All we do here is replace that macro with a JMP opcode..
|
|
|
|
`Listing 1.25: CPHX Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CBRA *
|
|
* *
|
|
* 65C02 BRA FAKE INSTRUCTION *
|
|
* *
|
|
* CYCLES: 4 *
|
|
* BYTES: 4 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CBRA MAC ; BRANCH ALWAYS
|
|
JMP ]1
|
|
<<<
|
|
|
|
```
|
|
|
|
### THE CPHX MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CPHX` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Push **.X** to the stack |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 11 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`CPHX` stands for "65**_C_**02 PusH **.X**, functionality that is not available in the original 6502 instruction set. Due to swapping values in the registers, this does take more cycles than desirable, so when possible just use the `PHA` instruction like every other normal human being.
|
|
|
|
`Listing 1.26: CPHX Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CPHX *
|
|
* *
|
|
* FAKE 65C02 PUSH .X TO STACK *
|
|
* *
|
|
* CYCLES: 11 *
|
|
* BYTES: 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CPHX MAC ; PUSH .X TO STACK
|
|
STA SCRATCH ; {3C2B} BACKUP .A CONTENTS
|
|
TXA ; {2C1B} TRANSFER .X TO .A {NZ}
|
|
PHA ; {3C1B} PUSH .A
|
|
LDA SCRATCH ; {3C2B} RESTORE .A CONTENTS {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE CPHY MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CPHY` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Push **.Y** to the stack |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 1 2 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`CPHY` stands for "65**_C_**02 PusH **.Y**," functionality that is not available in the original 6502 instruction set. Due to swapping values in the registers, this does take more cycles than desirable, so when possible just use the `PHA` instruction like every other normal human being.
|
|
|
|
`Listing 1.27: CPHY Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CPHY *
|
|
* *
|
|
* FAKE 65C02 PUSH .Y TO STACK *
|
|
* *
|
|
* CYCLES: 12 *
|
|
* BYTES: 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CPHY MAC ; PUSH .Y TO STACK
|
|
STA SCRATCH ; {3C2B} BACKUP .A
|
|
TYA ; {2C1B} TRANSFER .Y TO .A {NZ}
|
|
PHA ; {3C1B} PUSH .A TO STACK
|
|
LDA SCRATCH ; {3C2B} RESTORE .A CONTENTS {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
### THE CPLX MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CPLX` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Pull from stack and store in **.X** |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 11 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`CPLX` stands for "65**_C_**02 Pul**L .X**, functionality that is not available in the original 6502 instruction set. Due to swapping values in the registers, this does take more cycles than desirable, so when possible just use the `PLA` instruction like every other normal human being. Otherwise, this macro pulls the top value from the stack and stores it in **.X**.
|
|
|
|
`Listing 1.28: CPLX Macro and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CPLX *
|
|
* *
|
|
* FAKE 65C02 PULL .X TO STACK *
|
|
* *
|
|
* CYCLES: 11 *
|
|
* BYTES: 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CPLX MAC ; PULL FROM STACT TO .X
|
|
STA SCRATCH ; {3C2B} BACKUP .A
|
|
PLA ; {3C1B} PULL FROM STACK INTO .A {NZ}
|
|
TAX ; {2C1B} TRANSFER .A TO .X {NZ}
|
|
LDA SCRATCH ; {3C2B} RESTORE .A CONTENTS {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
### THE CPLY MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CPLY` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Pull from stack and store in **.Y** |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 11 |
|
|
| Bytes | 7 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`CPLY` stands for "65**_C_**02 PulL **.Y**, functionality that is not available in the original 6502 instruction set. Due to swapping values in the registers, this does take more cycles than desirable, so when possible just use the `PLA` instruction like every other normal human being. Otherwise, this macro pulls the top value from the 6502 stack and stores it in **.Y**.
|
|
|
|
`Listing 1.29: CPLY Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CPLY *
|
|
* *
|
|
* FAKE 65C02 PULL .Y FROM STAK *
|
|
* *
|
|
* CYCLES: 11 *
|
|
* BYTES: 7 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CPLY MAC ; PULL FROM STACK INTO .Y
|
|
STA SCRATCH ; {3C2B} BACKUP .A
|
|
PLA ; {3C1B} PULL FROM STACK TO .A {NZ}
|
|
TAY ; {2C2B} TRANSFER A TO .Y {NZ}
|
|
LDA SCRATCH ; {3C2B} RESTORE .A {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
### THE CTXY MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CTXY` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Transfer the contents of **.X** to **.Y** |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 10 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
Sadly, in regular 6502 there is no instruction for transferring the contents of **.X** to **.Y**, like there is in the 65c02 instruction set. This macro, which stands for "65**_C_**02 Transfer **.X** to **.Y**, fakes that functionality by using **.A** as an intermediary and then restoring its original value.
|
|
|
|
`Listing 1.30: CTXY Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CTXY *
|
|
* *
|
|
* TRANSFER X TO Y (FAKE 65C02) *
|
|
* *
|
|
* CYCLES: 10 *
|
|
* BYTES: 6 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CTXY MAC ; TRANSFER X TO Y
|
|
STA SCRATCH ; {3C2B} BACKUP .A
|
|
TXA ; {2C1B} TRANSFER .X TO .A {NZ}
|
|
TAY ; {2C1B} TRNSFER .A TO .Y {N}
|
|
LDA SCRATCH ; {3C2B} RESTORE .A {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
### THE CTYX MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `CTYX` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Transfer the contents of **.Y** to **.X** |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 10 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
Likewise, in regular 6502 there is no instruction for transferring the contents of **.Y** to **.X**, like there is in the 65c02 instruction set. This macro, which stands for "65**_C_**02 Transfer **.Y** to **.X**, fakes that functionality by using **.A** as an intermediary and then restoring its original value.
|
|
|
|
`Listing 1.31: CTXY Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CTYX *
|
|
* *
|
|
* TRANSFER Y TO X (FAKE 65C02) *
|
|
* *
|
|
* CYCLES: 10 *
|
|
* BYTE: 6 BYTES *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
CTYX MAC ; TRANSFER .Y TO .X
|
|
STA SCRATCH ; {3C2B} BACKUP .A
|
|
TYA ; {2C1B} TRANSFER .Y TO .A {NZ}
|
|
TAX ; {2C1B} TRANSFER .A TO .X {NZ}
|
|
LDA SCRATCH ; {3C2B} RESTORE .A {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
---
|
|
|
|
## 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 | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Delay a number of milliseconds |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 10 |
|
|
| Bytes | 6 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `CLRHI` macro clears the high nibble of the byte held in **.A**.
|
|
|
|
`Listing 1.32: DELAY Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* CLRHI *
|
|
* *
|
|
* Clear the high nibble of a *
|
|
* byte in **.A**. *
|
|
* *
|
|
* 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 DELAY MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `DELAY` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019N |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Delay a number of milliseconds |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 58+ |
|
|
| Bytes | 10 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `DELAY` macro calls the `DELAYMS` subroutine following this listing, which holds the 6502 CPU busy for a specified number of milliseconds by repeating through a specific number of cycles. It first backs up the **.Y** register for restoration after the delay, then calls the subroutine.
|
|
|
|
|
|
`Listing 1.33: DELAY Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* DELAY *
|
|
* *
|
|
* DELAY FOR PASSED MILLISECS *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = NUM OF MILLISECONDS *
|
|
* *
|
|
* CYCLES: 58+ *
|
|
* BYTES: 10 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
DELAY MAC ; DELAY FOR MILLISECONDS
|
|
STY SCRATCH ; {3C2B} BACKUP .Y
|
|
LDY ]1 ; {4C3B} # OF MILLISECONDS {NZ}
|
|
JSR DELAYMS ; {45C3B} DELAY SUBROUTINE {NZC}
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE DELAYMS SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `DELAYMS` |
|
|
| Type | Subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Delay a number of milliseconds |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 39+ |
|
|
| Bytes | 29 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `DELAYMS` subroutine uses a precise number cycles to delay for a number of given milliseconds. This is mostly adapted from Leventhal and Seville's _6502 Assembly Language Routines_.
|
|
|
|
It should be noted that this routine will only work correctly on a 6502 CPU that runs at 1.23 mhz; any other speed will slightly alter the delay.
|
|
|
|
`Listing 1.34: DELAYMS Subroutine and Heading`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* DELAYMS (LEVENTHAL/SEVILLE) *
|
|
* *
|
|
* ADAPTED FROM LEVANTHAL AND *
|
|
* SEVILLE'S /6502 ASSEMBLY *
|
|
* LANGUAGE ROUTINES/. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* .Y = NUMBER OF MILLISECS *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* DELAYS FOR X NUMBER OF *
|
|
* MILLISECONDS BY LOOPING *
|
|
* THROUGH A PRECISE NUMBER *
|
|
* OF CYCLES. *
|
|
* *
|
|
* DESTROYS: NZCDIV *
|
|
* ^^^ ^ *
|
|
* *
|
|
* CYCLES: 39+ *
|
|
* SIZE: 29 BYTES *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
DELAYMS
|
|
*
|
|
]MSCNT EQU $0CA ; LOOP 202 TIMES THROUGH DELAY1
|
|
; SPECIFIC TO 1.23 MHZ
|
|
; SPEED OF APPLE II
|
|
:DELAY
|
|
CPY #0 ; {2C2B} IF Y = 0, THEN EXIT {NZC}
|
|
BEQ :EXIT ; {2C|3C2B}
|
|
NOP ; {2C1B} (MAKE OVERHEAD=25C)
|
|
*
|
|
** IF DELAY IS 1MS THEN GOTO LAST1
|
|
** THIS LOGIC IS DESIGNED TO BE
|
|
** 5 CYCLES THROUGH EITHER ATH
|
|
*
|
|
CPY #1 ; {2C2B} USE 2 CYCLES {NZC}
|
|
BNE :DELAYA ; {2C|3C2B} 3C IF TAKEN, ELSE 2C
|
|
JMP :LAST1 ; {3C3B}
|
|
*
|
|
** DELAY 1 MILLISENCOND TIMES (Y-1)
|
|
*
|
|
:DELAYA
|
|
DEY ; {2C1B} (PREDEC Y) {NZ}
|
|
:DELAY0
|
|
LDX #]MSCNT ; {2C2B} {NZ}
|
|
:DELAY1
|
|
DEX ; {2C1B} {NZ}
|
|
BNE :DELAY1 ; {3C2B}
|
|
NOP ; {2C1B}
|
|
NOP ; {2C1B}
|
|
DEY ; {2C1B} {NZ}
|
|
BNE :DELAY0 ; {3C2B}
|
|
:LAST1
|
|
*
|
|
** DELAY THE LAST TIME 25 CYCLES
|
|
** LESS TO TAKE THE CALL, RETURN,
|
|
** AND ROUTINE OVERHEAD INTO
|
|
** ACCOUNT.
|
|
*
|
|
LDX #]MSCNT-3 ; {2C2B} {NZ}
|
|
:DELAY2
|
|
DEX ; {2C1B} {NZ}
|
|
BNE :DELAY2 ; {3C2B}
|
|
:EXIT
|
|
RTS ; {6C1B}
|
|
```
|
|
|
|
### THE DUMP MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `DUMP` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Dump value in mem block to screen |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 811+ |
|
|
| Bytes | 16 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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.35: DUMP Macro and Heading`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* DUMP *
|
|
* *
|
|
* DUMP THE HEX AT A GIVEN *
|
|
* ADDRESS. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* ]2 = LENGTH IN BYTES *
|
|
* *
|
|
* CYCLES: 811+ *
|
|
* BYTES: 16 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
DUMP MAC ; DUMP MEMORY CONTENTS
|
|
_AXLIT ]1 ; {8C6B} CHECK LITERAL
|
|
STY SCRATCH ; {3C2B} BACKUP .Y {NZ}
|
|
LDY ]2 ; {4C3B} LOAD .Y WITH LENGTH {NZ}
|
|
JSR __DUMP ; {794C3B} DUMP CONTENTS {NZCV}
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
### THE DUMP SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `__DUMP` |
|
|
| Type | Subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Dump value in mem block to screen |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 787+ |
|
|
| Bytes | 111 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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.36: DUMP Subroutine`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* __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 ADDR1 ; {3C2B} ADDRESS LOBYTE IN .A
|
|
STX ADDR1+1 ; {3C2B} ADDRESS HIBYTE IN .X
|
|
LDA #$8D ; {2C2B} LOAD CARRIAGE RETURN {NZ}
|
|
JSR ]COUT ; {89C3B} SEND TO COUT {NZCV}
|
|
LDA ADDR1+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 ADDR1+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 ADDR1 ; {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 ADDR1 ; {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 (ADDR1),Y ; {6C2B} LOAD BYTE FROM ADDRESS {NZ}
|
|
CLRHI ; {22C13B} AT COUNTER OFFSET; CLEAR HIBITS
|
|
STA ]LEFT ; {4C3B} SAVE LEFT INDEX
|
|
LDA (ADDR1),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 ERRH MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `ERRH` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Change Applesoft Error Handling Routine hook |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 73 |
|
|
| Bytes | 7 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
`ERRH` is a macro that sets the address that would be pointed to in the case of an error-handling issue in Applesoft \(and sometimes in DOS\). This is often used in conjunction with file handline routines, since many of those are to be found Applesoft. First the **.Y** register is backed up for later restoration, then calls the `_AXLIT` macro to pass parameters to the `__ERRH` subroutine. The **.Y** register is then restored to its original value, and control is returned to the main program.
|
|
|
|
`Listing 1.37: ERRH Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* ERRH *
|
|
* *
|
|
* SET THE ERROR HANDLING HOOK *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* *
|
|
* CYCLES: 73 *
|
|
* BYTES : 7 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
ERRH MAC ; SET ERROR HANDLE
|
|
STY SCRATCH ; {3C2B} BACKUP .Y
|
|
_AXLIT ; {8C6B} CHECK IF LITERAL
|
|
JSR __ERRH ; {59C3B} RUN ERROR HANDLE SET {NZ}
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {ZC}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE __ERRH SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `__ERRH` |
|
|
| Type | Subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Set error handling hook |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 53 |
|
|
| Bytes | 32 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `__ERRH` subroutine tricks DOS into thinking it is in immediate mode, turns on error handling, then defines the hook to which an error will point to. Control is then returned to the calling routine.
|
|
|
|
`Listing 1.38: __ERRH Subroutine`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* __ERRH (NATHAN RIGGS) *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* .A = ADDRESS LOBYTE *
|
|
* .X = ADDRESS HIBYTE *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* SETS NEW ADDRESS FOR THE *
|
|
* APPLSOFT ERROR HANDLING *
|
|
* ROUTINE. *
|
|
* *
|
|
* DESTROYS: NZCIDV *
|
|
* ^^ *
|
|
* *
|
|
* CYCLES: 53 *
|
|
* SIZE: 32 BYTES *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
__ERRH
|
|
LDA #1 ; {2C2B} TRICK DOS INTO THINKING {NZ}
|
|
STA $AAB6 ; {4C3B} IT'S IN APPLESOFT MODE
|
|
STA $75+1 ; {3C2B} APPLESOFT LINE NUMBER POINTER
|
|
STA $33 ; {3C2B} APPLESOFT PROMPT CHARACTER
|
|
STA ADDR1 ; {3C2B} ADDRESS LOBYTE IN .A
|
|
STX ADDR1+1 ; {3C2B} ADDRESS HIBYTE IN .X
|
|
LDA #$FF ; {2C2B} TURN ON ERROR HANDLING {NZ}
|
|
STA $D8 ; {3C3B} BYTE HERE
|
|
LDY #0 ; {2C2B} CLEAR OFFSET {NZ}
|
|
LDA (ADDR1),Y ; {6C2B} LOAD ADDRESS LOBYTE {NZ}
|
|
STA $9D5A ; {4C3B} SET AS ERROR HANDLING LO
|
|
INY ; {2C1B} INCREASE OFFSET {NZ}
|
|
LDA (ADDR1),Y ; {6C2B} LOAD ADDRESS HIBYTE {NZ}
|
|
STA $9D5B ; {4C3B} SET AS ERROR HANDLING HI
|
|
RTS ; {6C1B} EXIT SUBROUTINE
|
|
```
|
|
|
|
|
|
### THE GBIT MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `GBIT` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | get value of specific bit in byte |
|
|
| Input | none |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 14 |
|
|
| Bytes | 15 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_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.39: GBIT Subroutine`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* GBIT *
|
|
* *
|
|
* GET BIT FROM REG / ADDR BYTE *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = BYTE TO GET BIT FROM *
|
|
* *
|
|
* CYCLES: 14 *
|
|
* BYTES : 15 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
GBIT MAC ; GET A SINGLE BIT FROM BYTE
|
|
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 GRET MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `GRET` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | transfer `RETURN` data to another address |
|
|
| Input | ]1 = memory destination address |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 43+ |
|
|
| Bytes | 27 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `GRET` macro pulls the data held in `RETURN` and stores it in an alternative address. The length to be copied is to be determined by the `RETLEN` byte, which precedes `RETURN` in memory.
|
|
|
|
`Listing 1.40: GRET Macro`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* GRET *
|
|
* *
|
|
* COPY THE VALUE IN RETURN AND *
|
|
* PLACE IT IN GIVEN ADDRESS. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = MEMORY ADDRESS BYTE *
|
|
* *
|
|
* CYCLES: 43+ *
|
|
* BYTES : 27 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
GRET MAC ; GET RETURN VALUE
|
|
STY SCRATCH ; {3C2B} BACKUP .Y
|
|
_AXLIT ]1 ; {8C6B} CHECK LITERAL
|
|
STA ADDR1 ; {3C2B} LOBYTE PASSED IN .A
|
|
STX ADDR1+1 ; {3C2B} HIBYTE PASSED IN .X
|
|
LDY #255 ; {2C2B} RESET COUNTER {ZC}
|
|
]LP
|
|
INY ; {2C1B} INCREASE COUNTER {ZC}
|
|
LDA RETURN,Y ; {5C3B} LOAD BYTE IN RETURN AT {ZC}
|
|
STA (ADDR1),Y ; {6C2B} COUNTER OFFSET; STORE AT
|
|
CPY RETLEN ; {4C3B} NEW LOCATION {NZC}
|
|
BNE ]LP ; {4C2B} IF COUNTER < RETLEN, LOOP
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
## MEMORY MACROS AND SUBROUTINES
|
|
|
|
The remaining macros and subroutines are dedicated to memory manipulation, which is such a common need in most routines that their inclusion here is deemed necessary. Note that rior to this version of the collection, memory macros and subroutines were considered a library separate from the required library, though they shared the same disk.
|
|
|
|
### THE PEEK MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `PEEK` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | loads a value held at an address in **.A** |
|
|
| Input | ]1 = memory address to read |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 28 |
|
|
| Bytes | 18 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `PEEK` macro is simply a macro that loads the **.A** register with the value at a given address. It may serve a greater purpose in the future.
|
|
|
|
`Listing 1.41: PEEK Macro`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* PEEK *
|
|
* *
|
|
* LOAD .A WITH VALUE AT ADDR *
|
|
* *
|
|
* CYCLES: 28 *
|
|
* BYTES: 18 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
PEEK MAC ; PUT VALUE INTO .A
|
|
STY SCRATCH ; {3C2B}
|
|
_AXLIT ]1 ; {8C6B}
|
|
STA ADDR1 ; {3C2B}
|
|
STX ADDR1+1 ; {3C2B}
|
|
LDY #0 ; {2C2B} {ZC}
|
|
LDA (ADDR1),Y ; {6C2B} {ZC}
|
|
LDY SCRATCH ; {3C2B} {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE POKE MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `POKE` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | stores a value held in **.A** at given address|
|
|
| Input | ]1 = memory address to store value |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 32 |
|
|
| Bytes | 20 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `POKE` macro is simply a macro that loads the **.A** register with the value at a given address. It may serve a greater purpose in the future.
|
|
|
|
`Listing 1.42: POKE Macro`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* POKE *
|
|
* *
|
|
* STORE VALUE AT ADDRESS *
|
|
* *
|
|
* CYCLES: 4 *
|
|
* BYTES: 3 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
POKE MAC ; STORE .A INTO MEM LOC
|
|
STY SCRATCH ; {3C2B}
|
|
_AXLIT ]2 ; {8C6B}
|
|
STA ADDR1 ; {3C2B}
|
|
STX ADDR1+1 ; {3C2B}
|
|
LDA ]1 ; {3C2B} {ZC}
|
|
LDY #0 ; {3C2B} {ZC}
|
|
STA (ADDR1),Y ; {6C2B} {ZC}
|
|
LDY SCRATCH ; {3C2B} {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE MFILL MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MFILL` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Fill a block of memory |
|
|
| Input | starting address, length, fill value |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 110+ |
|
|
| Bytes | 36 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `MFILL` macro takes a starting address a length byte, and a fill value, and fill a block of memory starting at the address given with the specified length and fill value. First, **.Y** is backed up to be restored after calling the `MEMFILL` subroutine, then the parameters are sorted using the `_MLIT` macro for parameters passed via the zero mage. Another parameter is stored in `BPAR1`, then `MEMFILL` is called to complete the task. Once the subroutine returns control back to the macro, **.Y** is restored to its original value.
|
|
|
|
`Listing 1.43: MFILL Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MFILL *
|
|
* *
|
|
* FILL BLOCK OF MEMORY WITH *
|
|
* SPECIFIED VALUE. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = STARTING ADDRESS *
|
|
* ]2 = LENGTH IN BYTES *
|
|
* ]3 = FILL VALUE *
|
|
* *
|
|
* CYCLES: 110+ *
|
|
* BYTES: 36 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
MFILL MAC ; MEMORY FILL
|
|
STY SCRATCH ; {3C2B} BACKUP .Y REGISTER
|
|
_MLIT ]1;WPAR1 ; {16C12B} CHECK LITERAL OF ]1
|
|
_MLIT ]2;WPAR2 ; {16C12B} CHECK LITERAL OF ]2
|
|
LDA ]3 ; {4C3B} FILL VALUE {NZ}
|
|
STA BPAR1 ; {3C2B} STORED IN BPAR1
|
|
JSR MEMFILL ; {6C3B59+} GOSUB ROUTINE
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
```
|
|
|
|
|
|
### THE MEMFILL SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MEMFILL` |
|
|
| Type | Subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Fill a block of memory |
|
|
| Input | starting address, length, fill value |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZ |
|
|
| Cycles | 59+ |
|
|
| Bytes | 31 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `MEMFILL` subroutine is usualy called by `MFILL` in order to fill block of memory with a given value. Note that this can span more than a single page, and fills whole pages before it continues to fill the pages that are not fully overwritten. Also note that the main algorithm for this was waken from Leventhal & Saville's _6502 Assembly Routines_, as it works very well and there is no need, at this time, to completely reinvent the wheel.
|
|
|
|
`Listing 1.44: MFILL Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MEMFILL (LEVENTHAL/SAVILLE) *
|
|
* *
|
|
* ADAPTED FROM LEVANTHAL AND *
|
|
* SAVILLE'S /6502 ASSEMBLY *
|
|
* LANGUAGE ROUTINES/. FILLS A *
|
|
* SPECIFIED RANGE OF MEMORY *
|
|
* WITH A FILL VALUE. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* BPAR1 = FILL VALUE *
|
|
* WPAR2 = FILL LENGTH *
|
|
* WPAR3 = STARTING ADDRESS *
|
|
* *
|
|
* DESTROYS: NZCIDV *
|
|
* ^^ *
|
|
* *
|
|
* CYCLES: 59+ *
|
|
* SIZE: 31 BYTES *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
]FILL EQU BPAR1 ; FILL VALUE
|
|
]SIZE EQU WPAR2 ; RANGE LENGTH IN BYTES
|
|
]ADDR EQU WPAR1 ; RANGE STARTING ADDRESS
|
|
*
|
|
MEMFILL
|
|
*
|
|
** FILL WHOLE PAGES FIRST
|
|
*
|
|
LDA ]FILL ; {3C2B} GET VAL FOR FILL {NZ}
|
|
LDX ]SIZE+1 ; {3C2B} X=# OF PAGES TO DO {NZ}
|
|
BEQ :PARTPG ; {3C2B} BRANCH IF HIGHBYTE OF SZ = 0
|
|
LDY #0 ; {2C2B} RESET INDEX {NZ}
|
|
:FULLPG
|
|
STA (]ADDR),Y ; {6C2B} FILL CURRENT BYTE
|
|
INY ; {2C1B} INCREMENT INDEX {NZ}
|
|
BNE :FULLPG ; {3C2B} BRANCH IF NOT DONE W/ PAGE
|
|
INC ]ADDR+1 ; {5C2B} ADVANCE TO NEXT PAGE {NZ}
|
|
DEX ; {2C1B} DECREMENT COUNTER {NZ}
|
|
BNE :FULLPG ; {3C2B} BRANCH IF NOT DONE W/ PAGES
|
|
*
|
|
** DO THE REMAINING PARTIAL PAGE
|
|
** REGISTER A STILL CONTAINS VALUE
|
|
*
|
|
:PARTPG
|
|
LDX ]SIZE ; {3C2B} GET # OF BYTES IN LAST PAGE {NZ}
|
|
BEQ :EXIT ; {3C2B} BRANCH IF LOW BYTE = 0
|
|
LDY #0 ; {2C2B} RESET INDEX {NZ}
|
|
:PARTLP
|
|
STA (]ADDR),Y ; {6C2B} STORE VAL
|
|
INY ; {2C1B} INCREMENT INDEX {NZ}
|
|
DEX ; {2C1B} DECREMENT COUNTER {NZ}
|
|
BNE :PARTLP ; {3C2B} BRANCH IF NOT DONE
|
|
:EXIT
|
|
RTS ; {6C1B}
|
|
```
|
|
|
|
### THE MOVB MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MOVB` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | |
|
|
| Output | none |
|
|
| Dependencies | `MEMMOVE` |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 267+ |
|
|
| Bytes | 48 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `MOVB` macro moves an number of blocks of memory from a source address to a destination address with a given length. The macro first backs up the **.Y** register, then runs a series of `_MLIT` macros to prepare for the passing of parameters to the `MEMMOVE` subroutine via the zero page. The subroutine is then called, after which the **.Y** register is restored to its original value.
|
|
|
|
`Listing 1.46: MOVB Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MOVB *
|
|
* *
|
|
* MOVE A BLOCK OF MEMORY FROM *
|
|
* A SOURCE TO DESTINATION. *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = SOURCE ADDRESS *
|
|
* ]2 = DESTINATION ADDRESS *
|
|
* ]3 = NUMBER OF BYTES *
|
|
* *
|
|
* CYCLES: 267+ *
|
|
* BYTES: 48 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
MOVB MAC ; MEMORY MOVE
|
|
STY SCRATCH ; {3C2B} BACKUP .Y
|
|
_MLIT ]1;WPAR1 ; {16C12B} CHECK 1ST ADDR AS LITERAL
|
|
_MLIT ]2;WPAR2 ; {16C12B} CHECK 2ND ADDR AS LITERAL
|
|
_MLIT ]3;WPAR3 ; {16C12B} CHECK 3RD AS LITERAL
|
|
JSR MEMMOVE ; {213C3B} GOSUB ROUTINE {NZCV}
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MACRO
|
|
```
|
|
|
|
### THE MEMMOVE SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MEMMOVE` |
|
|
| Type | subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | |
|
|
| Input | |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZCV |
|
|
| Cycles | 207+ |
|
|
| Bytes | 117 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
This is another subroutine lifted from Leventhal and Seville's _6502 Assembly Language Routines_. While moving memory contents on its surface does not seem like a terribly complicated process, this implementation makes sure to work with overlaps in copying, etc. in a mostly efficient way. Currently, this author would be able to do no better than Leventhal and Seville here, and thus the subroutine stays. Of course, this may be altered later to a point where Seville & Leventhal's work is no longer required.
|
|
|
|
Ultimately, the `MEMMOVE` subroutine moves a block of memory at a certain length to another adfress. This block of memory can span multiple pages, and thanks to the way the subroutine functions, overlaps in memory allocation should not be a concern.
|
|
|
|
`Listing 1.47: MEMMOVE Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MEMMOVE (LEVENTHAL/SEVILLE) *
|
|
* *
|
|
* ADAPTED FROM LEVANTHAL AND *
|
|
* SEVILLE'S /6502 ASSEMBLY *
|
|
* LANGUAGE ROUTINES/. COPIES *
|
|
* SERIES OF BYTES FROM SRCLOC *
|
|
* TO DESTLOC. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* WPAR3 = LENGTH IN BYTES *
|
|
* WPAR1 = SOURCE ADDRESS *
|
|
* WPAR2 = DESTINATION ADDRESS *
|
|
* *
|
|
* DESTROY: NZCIDV *
|
|
* ^^^ ^ *
|
|
* *
|
|
* CYCLES: 207+ *
|
|
* BYTES: 117 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
]SIZE EQU WPAR3 ; LENGTH TO COPY (BYTES)
|
|
]ADDR1 EQU WPAR1 ; SOURCE ADDRESS
|
|
]ADDR2 EQU WPAR2 ; DESTINATION ADDRESS
|
|
*
|
|
MEMMOVE
|
|
*
|
|
** DETERMINE IF DEST AREA IS
|
|
** ABOVE SRC AREA BUT OVERLAPS
|
|
** IT. REMEMBER, OVERLAP CAN BE
|
|
** MOD 64K. OVERLAP OCCURS IF
|
|
** STARTING DEST ADDRESS MINUS
|
|
** STARTING SRC ADDRESS (MOD
|
|
** 64K) IS LESS THAN NUMBER
|
|
** OF BYTES TO MOVE.
|
|
*
|
|
LDA ]ADDR2 ; {3C2B} CALC DEST-SRC {NZ}
|
|
SEC ; {2C1B} SET CARRY {C=1}
|
|
SBC ]ADDR1 ; {3C2B} SUBTRACT SOURCE ADDRESS {NZCV}
|
|
TAX ; {2C1B} HOLD VAL IN .X {NZ}
|
|
LDA ]ADDR2+1 ; {3C2B} {NZ}
|
|
SBC ]ADDR1+1 ; {3C2B} MOD 64K AUTOMATIC {NZCV}
|
|
; -- DISCARD CARRY
|
|
TAY ; {2C1B} HOLD HIBYTE IN .Y {NZ}
|
|
TXA ; {2C1B} CMP LOBYTE WITH # TO MOVE {NZ}
|
|
CMP ]SIZE ; {3C2B} {NZC}
|
|
TYA ; {2C1B} {NZ}
|
|
SBC ]SIZE+1 ; {3C2B} SUBTRACT SIZE+1 FROM HIBYTE {NZCV}
|
|
BCS :DOLEFT ; {3C2B} BRANCH IF NO OVERLAP
|
|
*
|
|
** DEST AREA IS ABOVE SRC AREA
|
|
** BUT OVERLAPS IT.
|
|
** MOVE FROM HIGHEST ADDR TO
|
|
** AVOID DESTROYING DATA
|
|
*
|
|
JSR :MVERHT ; {3C6B}
|
|
JMP :MREXIT ; {3C3B}
|
|
*
|
|
** NO PROB DOING ORDINARY MOVE
|
|
** STARTING AT LOWEST ADDR
|
|
*
|
|
:DOLEFT
|
|
JSR :MVELEFT ; {6C3B}
|
|
:EXIT
|
|
JMP :MREXIT ; {3C3B}
|
|
:MVELEFT
|
|
LDY #0 ; {2C2B} ZERO INDEX {NZ}
|
|
LDX ]SIZE+1 ; {3C2B} X=# OF FULL PP TO MOVE {NZ}
|
|
BEQ :MLPART ; {3C2B} IF X=0, DO PARTIAL PAGE
|
|
:MLPAGE
|
|
LDA (]ADDR1),Y ; {6C2B} LOAD BYTE FROM SOURCE {NZ}
|
|
STA (]ADDR2),Y ; {6C2B} MOVE BYTE TO DESTINATION
|
|
INY ; {2C1B} NEXT BYTE {NZ}
|
|
BNE :MLPAGE ; {3C2B} CONT UNTIL 256B MOVED
|
|
INC ]ADDR1+1 ; {5C2B} ADV TO NEXT SRC PAGE {NZ}
|
|
INC ]ADDR2+1 ; {5C2B} ADV NEXT DEST PAGE {NZ}
|
|
DEX ; {2C1B} DEC PAGE COUNT {NZ}
|
|
BNE :MLPAGE ; {3C2B} CONT UNTIL ALL FULL
|
|
; PAGES ARE MOVED
|
|
:MLPART
|
|
LDX ]SIZE ; {3C2B} GET LENGTH OF LAST PAGE {NZ}
|
|
BEQ :MLEXIT ; {3C2B} BR IF LENGTH OF LAST
|
|
; PAGE = 0
|
|
; REG Y IS 0
|
|
:MLLAST
|
|
LDA (]ADDR1),Y ; {6C2B} LOAD BYTE FROM SOURCE {NZ}
|
|
STA (]ADDR2),Y ; {6C2B} MOVE BYTE TO DESTINATION
|
|
INY ; {2C1B} NEXT BYTE {NZ}
|
|
DEX ; {2C1B} DEC COUNTER {NZ}
|
|
BNE :MLLAST ; {3C2B} CONT UNTIL LAST P DONE
|
|
:MLEXIT
|
|
JMP :MREXIT ; {3C3B}
|
|
*
|
|
********************************
|
|
*
|
|
:MVERHT
|
|
*
|
|
** -- MOVE THE PARTIAL PAGE FIRST
|
|
*
|
|
LDA ]SIZE+1 ; {3C2B} GET SIZE HIBYTE {NZ}
|
|
CLC ; {2C1B} CLEAR CARRY {C=0}
|
|
ADC ]ADDR1+1 ; {3C2B} ADD SOURCE ADDRESS HIBYTE {NZCV}
|
|
STA ]ADDR1+1 ; {3C2B} POINT TO LAST PAGE OF SRC
|
|
LDA ]SIZE+1 ; {3C2B} GET SIZE HIBYTE {NZ}
|
|
CLC ; {2C1B} CLEAR CARRY {C=0}
|
|
ADC ]ADDR2+1 ; {3C2B} ADD DESTINATION HIBYTE {NZCV}
|
|
STA ]ADDR2+1 ; {3C2B} POINT TO LAST P OF DEST
|
|
*
|
|
** MOVE THE LAST PARTIAL PAGE FIRST
|
|
*
|
|
LDY ]SIZE ; {3C2B} GET LENGTH OF LAST PAGE {NZ}
|
|
BEQ :MRPAGE ; {3C2B} IF Y=0 DO THE FULL PAGES
|
|
:MR0
|
|
DEY ; {2C1B} BACK UP Y TO NEXT BYTE {NZ}
|
|
LDA (]ADDR1),Y ; {6C2B} LOAD CURRENT SOURCE BYTE {NZ}
|
|
STA (]ADDR2),Y ; {6C2B} STORE IN CURRENT DESTINATION
|
|
CPY #0 ; {2C2B} BRANCH IF NOT DONE {NZC}
|
|
BNE :MR0 ; {3C2B} WITH THE LAST PAGE
|
|
:MRPAGE
|
|
LDX ]SIZE+1 ; {3C2B} GET SIZE HIBYTE {NZ}
|
|
BEQ :MREXIT ; {3C2B} BR IF HYBYTE = 0 (NO FULL P)
|
|
:MR1
|
|
DEC ]ADDR1+1 ; {5C2B} BACK UP TO PREV SRC PAGE {NZ}
|
|
DEC ]ADDR2+1 ; {5C2B} TO DEST {NZ}
|
|
:MR2
|
|
DEY ; {2C1B} BACK UP Y TO NEXT BYTE {NZ}
|
|
LDA (]ADDR1),Y ; {6C2B} LOAD SOURCE CURRENT BYTE {NZ}
|
|
STA (]ADDR2),Y ; {6C2B} STORE BYTE IN DESTINATION
|
|
CPY #0 ; {2C2B} IF NOT DONE WITH PAGE {NZC}
|
|
BNE :MR2 ; {3C2B} THEN BRANCH OUT
|
|
DEX ; {2C1B} DECREASE BYTE COUNTER {NZ}
|
|
BNE :MR1 ; {3C2B} BR IF NOT ALL PAGES MOVED
|
|
:MREXIT
|
|
RTS ; {6C1B}
|
|
```
|
|
|
|
### THE MSWAP MACRO
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MSWAP` |
|
|
| Type | Macro |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Swap one memory block with another |
|
|
| Input | |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 98+ |
|
|
| Bytes | 36 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `MSWAP` macro swaps a given block of memory with another block of memory. Note that unlike most other memory routines here, this can only work with up to 255 bytes, and there is no error checking for overlaps. These issues will be addressed in the future, of course, but it would be best to keep this in mind until then.
|
|
|
|
The macro first backs up the contents of the **.Y** register, then makes several calls to the `_MLIT` macro to sort out parameters on the zero page. **.A** is then loaded with the length in bytes of the swap and stored in `BPAR1` as a parameter before the `MEMSWAP` subroutine is called. Afterwards, control is returned to the main program and the original contents of **.Y** are restored.
|
|
|
|
`Listing 1.48: MSWAP Macro`
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MSWAP *
|
|
* *
|
|
* SWAPS THE VALUES STORED IN *
|
|
* ONE LOCATION WITH ANOTHER *
|
|
* *
|
|
* PARAMETERS *
|
|
* *
|
|
* ]1 = FIRST ADDRESS *
|
|
* ]2 = SECOND ADDRESS *
|
|
* ]3 = LENGTH IN BYTES (BYTE) *
|
|
* *
|
|
* CYCLES: 98+ *
|
|
* BYTES: 36 *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
MSWAP MAC ; MEMORY SWAP
|
|
STY SCRATCH ; {3C2B} BACKUP .Y
|
|
_MLIT ]2;WPAR2 ; {16C12B} TEST AS LITERAL
|
|
_MLIT ]1;WPAR1 ; {16C12B} TEST AS LITERAL
|
|
LDA ]3 ; {4C3B} {NZ}
|
|
STA BPAR1 ; {3C2B} STORE LENGTH IN BYTES
|
|
JSR MEMSWAP ; {6C3B46+} GOSUB ROUTINE {NZC}
|
|
LDY SCRATCH ; {3C2B} RESTORE .Y {NZ}
|
|
<<< ; END MAC
|
|
*
|
|
```
|
|
### THE MEMSWAP SUBROUTINE
|
|
|
|
_SUMMARY_
|
|
|
|
| Condition | Value |
|
|
| --- | --- |
|
|
| Name | `MEMSWAP` |
|
|
| Type | Subroutine |
|
|
| Author | Nathan Riggs |
|
|
| Last Revision | 12-DEC-2019 |
|
|
| Assembler | Merlin 8 Pro |
|
|
| OS | Apple DOS 3.3 |
|
|
| Purpose | Swap one memory block with another |
|
|
| Input | |
|
|
| Output | none |
|
|
| Dependencies | none |
|
|
| Flags Destroyed | NZC |
|
|
| Cycles | 46+ |
|
|
| Bytes | 18 |
|
|
| Notes | |
|
|
|
|
---
|
|
|
|
_DETAILS_
|
|
|
|
The `MEMSWAP` subroutine swaps one block of memory less than a single page to another block of memory. No error checking is applied, especially for cases like memory overlap, so care should be taken with the subroutine as it currently stands.
|
|
|
|
`Listing 1.49: MEMSWAP SUBROUTINE`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* MEMSWAP (NATHAN RIGGS) *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* BPAR1 = SWAP LENGTH *
|
|
* WPAR1 = 1ST ADDRESS *
|
|
* WPAR2 = 2ND ADDRESS *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* SWAPS THE VALUES IN THE *
|
|
* MEMORY LOCATIONS GIVEN *
|
|
* FOR THE SPECIFIED LENGTH. *
|
|
* *
|
|
* DESTROYS: NZCIDV *
|
|
* ^^^ *
|
|
* *
|
|
* CYCLES: 46+ *
|
|
* SIZE: 18 BYTES *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
]SIZE EQU BPAR1 ; SIZE OF RANGE TO SWAP
|
|
]ADDR1 EQU WPAR1 ; SOURCE ADDRESS 1
|
|
]ADDR2 EQU WPAR2 ; SOURCE ADDRESS 2
|
|
*
|
|
MEMSWAP
|
|
LDY #255 ; {2C2B} RESET BYTE INDEX {NZ}
|
|
:LP
|
|
INY ; {2C1B} INCREASE BYTE INDEX {NZ}
|
|
LDA (]ADDR1),Y ; {6C2B} LOAD BYTE FROM FIRST ADDRESS {NZ}
|
|
TAX ; {2C1B} TRANSFER TO .X {NZ}
|
|
LDA (]ADDR2),Y ; {6C2B} LOAD BYTE FROM SECOND ADDRESS {NZ}
|
|
STA (]ADDR1),Y ; {6C2B} STORE IN FIRST ADDRESS
|
|
TXA ; {2C1B} TRANSFER FIRST BYTE VAL TO .A {NZ}
|
|
STA (]ADDR2),Y ; {6C2B} NOW STORE THAT IN SECOND ADDRESS
|
|
CPY ]SIZE ; {3C2B} IF BYTE INDEX < LENGTH, {NZC}
|
|
BNE :LP ; {3C2B} CONTINUE LOOPING
|
|
RTS ; {6C1B} OTHERWISE, EXIT
|
|
*
|
|
RTS ; {6C1B}
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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 |
|
|
| ------------------------- | ----------- | ------- | --------- |
|
|
| `CALL` | jsr equivalent | 3 | 6 |
|
|
| `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 |
|
|
| `RET` | `RTS` equivalent | 1 | 6 |
|
|
| `SCF` | `SEC` equivalent | 1 | 2 |
|
|
|
|
|
|
|
|
## Part III: REQUIRED LIBRARY DEMO FILE
|
|
|
|
At the the end of each library, 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.50: DEMO.REQUIRED`
|
|
|
|
```asm
|
|
*
|
|
*``````````````````````````````*
|
|
* DEMO.REQUIRED *
|
|
* *
|
|
* AUTHOR: NATHAN RIGGS *
|
|
* CONTACT: NATHAN.RIGGS@ *
|
|
* OUTLOOK.COM *
|
|
* *
|
|
* DATE: 11-DEC-2019 *
|
|
* 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
|
|
*
|
|
*``````````````````````````````*
|
|
* 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.
|
|
*
|
|
** USAGE:
|
|
JSR ]HOME
|
|
_PRN "THE _PRN MACRO",8D
|
|
_PRN "===============",8D8D
|
|
** _PRN "THIS IS A TEST.",8D8D
|
|
*
|
|
_PRN "THIS IS A TEST.",8D8D
|
|
*
|
|
* 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.
|
|
*
|
|
** USAGE:
|
|
** _WAIT
|
|
*
|
|
_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.
|
|
*
|
|
** USAGE:
|
|
** BEEP #255
|
|
*
|
|
_PRN "BEEEEEEEEEEEEEEP",8D8D
|
|
BEEP #255
|
|
*
|
|
*``````````````````````````````*
|
|
* LONG BRANCHING MACROS *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
*** LONG BRANCHING ROUTINES ARE PRETTY SELF-
|
|
*** EXPLANATORY TO ANYONE WHO HAS RUN INTO THE
|
|
*** PAGE LENGTH PROBLEM WITH BRANCHING INSTRUCTIONS.
|
|
*** ESSENTIALLY, THESE MACROS TO THE APPROPRIATE TESTS,
|
|
*** THEN JMP TO THE PARAMETER ADDRESS PASSED. NOTE THAT
|
|
*** THIS CAN EASILY BREAK CODE THAT IS SUPPOSED TO BE
|
|
*** RELOCATABLE.
|
|
*
|
|
** SINCE ALL OF THE LONG BRANCHING MACROS WORK
|
|
** EXACTLY THE SAME, IT IS ONLY NECESSARY TO SHOW
|
|
** HOW ONE OF THEM IS USED. WE WILL USE BCCL HERE,
|
|
** OR BRANCH CARRY CLEAR LONG.
|
|
*
|
|
** USAGE:
|
|
** CLC
|
|
** BCCL ]CONTINUE
|
|
** BRK
|
|
** ]CONTINUE
|
|
** _PRN "THE CARRY IS CLEAR.",8D8D
|
|
_WAIT
|
|
*
|
|
_PRN "THE BCCL MACRO",8D
|
|
_PRN "==============",8D8D
|
|
CLC
|
|
BCCL ]CONTINUE
|
|
BRK
|
|
]CONTINUE
|
|
_PRN "THE CARRY IS CLEAR.",8D8D
|
|
_WAIT
|
|
*
|
|
* THE BCCL MACRO - BRANCH CARRY CLEAR LONG
|
|
* THE BCSL MACRO - BRANCH CARRY SET LONG
|
|
* THE BEQL MACRO - BRANCH IF EQUAL LONG
|
|
* THE BMIL MACRO - BRANCH IF MINUS LONG
|
|
* THE BNEL MACRO - BRANCH NOT EQUAL LONG
|
|
* THE BPLL MACRO - BRANCH IF PLUS LONG
|
|
* THE BVCL MACRO - BRANCH OVERFLOW CLEAR LONG
|
|
* THE BVSL MACRO - BRANCH OVERFLOW SET LONG
|
|
*
|
|
*``````````````````````````````*
|
|
* FAKE 65CO2 MACROS *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
*** THE FAKE 65C02 MACROS ARE APROXIMATIONS
|
|
*** OF THE RELEVANT INSTRUCTIONS IN THE 65C02
|
|
*** INSTRUCTION SET WHEN YOU ONLY HAVE A 6502
|
|
*** TO WORK WITH. OBVIOUSLY, IF A 65C02 PROCESSOR
|
|
*** IS READILY AVAILABLE, YOU SHOULD USE THE
|
|
*** REAL INSTRUCTION SET.
|
|
*
|
|
* THE CBRA MACRO - 65C02 BRANCH ALWAYS
|
|
*
|
|
** THE CBRA MACRO SIMPLY REPLACES THE MACRO WITH
|
|
** A JMP INSTRUCTION TO EMULATE THE BRANCH ALWAYS (BRA)
|
|
** 65CO2 INSTRUCTION. IT WORKS LIKE YOU'D EXPECT:
|
|
*
|
|
** USAGE:
|
|
*
|
|
** CBRA ]CONT
|
|
** BRK
|
|
**]CONT _PRN "CBRA WORKED AS EXPECTED.",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE CBRA MACRO",8D
|
|
_PRN "==============",8D8D
|
|
CBRA ]CONT
|
|
BRK
|
|
]CONT _PRN "CBRA WORKED AS EXPECTED.",8D8D
|
|
_WAIT
|
|
*
|
|
* THE CPHX MACRO - 65C02 PUSH .X
|
|
*
|
|
** THE CPHX MACRO EMULATES THE 65C02 INSTRUCTION
|
|
** THAT PUSHES .X TO THE STACK.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDX #$FF
|
|
** CPHX
|
|
** _PRN "CONTENT PULLED FROM STACK: "
|
|
** PLA ; .X IS NOW IN .A
|
|
** STA $300
|
|
** DUMP #$300;#1
|
|
** _PRN " ",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE CPHX MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDX #$FF
|
|
CPHX
|
|
_PRN "CONTENT PULLED FROM THE STACK: "
|
|
PLA
|
|
STA $300
|
|
DUMP #$300;#1
|
|
_PRN " ",8D8D
|
|
_WAIT
|
|
*
|
|
* THE CPHY MACRO - 65C02 PUSH .Y
|
|
*
|
|
** THE CPHY MACRO WORKS THE SAME WAY AS THE CPHX
|
|
** MACRO, EXCEPT PUSHES .Y INSTEAD OF .X. THUS,
|
|
** IT WOULD BE REDUNDANT TO SHOW THE EXACT SAME PROCESS--
|
|
** TREAT THE MACRO THE SAME WAY.
|
|
*
|
|
* THE CPLX MACRO - 65C02 PULL .X
|
|
*
|
|
** THE CPLX MACRO EMULATES THE PLX INSTRUCTION
|
|
** FOR THE 65C02. THE VALUE AT THE TOP OF THE STACK
|
|
** IS PULLED AND THEN STORED IN .X.
|
|
*
|
|
** USAGE:
|
|
** LDA #$EE
|
|
** PHA
|
|
** CPLX
|
|
** STX $300
|
|
** _PRN "VALUE PULLED FROM STACK:"
|
|
** DUMP #$300;#1
|
|
** _WAIT
|
|
** _PRN " ",8D8D
|
|
*
|
|
_PRN "THE CPLX MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDA #$EE
|
|
PHA
|
|
CPLX
|
|
STX $300
|
|
_PRN "VALUE PULLED FROM STACK:"
|
|
DUMP #$300;#1
|
|
_WAIT
|
|
_PRN " ",8D8D
|
|
*
|
|
* THE CPLY MACRO - 65C02 PULL .Y
|
|
*
|
|
** THE CPLY MACRO, OR 65CO2 PULL .Y MACRO,
|
|
** EMULATES THE 65C02 INSTRUCTION PLY. AGAIN,
|
|
** THIS WORKS EXACTLY THE SAME AS CPLX, EXCEPT
|
|
** THE PULL IS STORED IN .Y INSTEAD OF .X.
|
|
** THEREFORE, WE NEED NOT BE REDUNDANT.
|
|
*
|
|
* THE CTXY MACRO - 65C02 TRANSFER .X TO .Y
|
|
*
|
|
** THE CTXY MACRO EMULATES THE 65C02 TRANSFER .X TO .Y
|
|
** INSTRUCTION, TXY. .A IS USED AS AN INTERMEDIARY, AND
|
|
** THEN ITS ORIGINAL VALUE IS RESTORED.
|
|
*
|
|
** USAGE:
|
|
** LDY #0
|
|
** LDX #$DD
|
|
** CTXY
|
|
** STY $300
|
|
** _PRN "CONTENT IN .Y:"
|
|
** DUMP #$300;#1
|
|
** _WAIT
|
|
** _PRN " ",8D8D
|
|
*
|
|
_PRN "THE CTXY MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDY #0
|
|
LDX #$DD
|
|
CTXY
|
|
STY $300
|
|
_PRN "CONTENT IN .Y:"
|
|
DUMP #$300;#1
|
|
_WAIT
|
|
_PRN " ",8D8D
|
|
*
|
|
* THE CTYX MACRO - 65C02 TRANSFER .Y TO .X
|
|
*
|
|
** LIKE MANY SUBROUTINES BEFORE, THE CTYX MACRO OR
|
|
** THE TRANSFER .Y TO .X MACRO, WORKS EXACTLY LIKE
|
|
** ITS RELATED SUBROUTINE, THE CTXY MACRO, AND THUS
|
|
** ITS USAGE SHOULD BE REFERRED TO IN THAT EXAMLPLE.
|
|
** THIS MACRO EMULATES THE CYX INSTRUCTION IN 65CO2.
|
|
*
|
|
*``````````````````````````````*
|
|
* MEMORY MANIPULATION MACROS *
|
|
*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*
|
|
*
|
|
*** THE MACROS DEDICATED TO MANIPULATING MEMORY
|
|
*** ARE USED FOR A VARIETY OF REASONS, BUT IF
|
|
*** YOU ARE A SEASONED PROGRAMMER, YOU ALREADY KNOW
|
|
*** THAT 99.9% OF THE JOB IS MEMORY MANIPULATION.
|
|
*** THESE HERE ENCOMPASS THOSE DEDICATED TO MOVING,
|
|
*** COPYING, AND FILLING MEMORY BLOCKS, AS WELL AS
|
|
*** VIEWING MEMORY CONTENTS FOR DEBUGGING PURPOSES.
|
|
*** FOR FUN, A POKE AND PEEK MACRO ARE INCLUDED FOR
|
|
*** THE APPLESOFT KIDS, AND LOADS OR PULLS THE VALUE INTO .A.
|
|
*
|
|
* 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:
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$BB
|
|
** STA $300
|
|
** LDA #$AA
|
|
** STA $301
|
|
** _PRN "VALUES IN $301-$302:"
|
|
** DUMP #$300;#2
|
|
** _WAIT
|
|
** _PRN " ",8D8D
|
|
*
|
|
_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 ERRH MACRO - ERROR HANDLER
|
|
*
|
|
** THE ERRH MACRO SETS THE ERROR HANDLING
|
|
** HOOK FOR SOUBROUTINES THAT RELY ON
|
|
** APPLESOFT IN SOME WAY. THIS IS PARTICULARLY
|
|
** USED TO DETECT AN END-OF-FILE WHEN
|
|
** READING A TEXT FILE, AS THERE IS NO "STANDARD"
|
|
** WAY TO DETECT THE EOF EXCEPT BY THE ERROR RETURN.
|
|
*
|
|
** USAGE:
|
|
** ERRH #$6000
|
|
*
|
|
** SINCE THIS ROUTINE REQUIRES TOO MUCH INTEGRATION
|
|
** WITH OTHER LIBRARIES, YOU WON'T BE SEEING A
|
|
** DEMO HERE. HOWEVER, THE CURRENT ITERATION OF THE
|
|
** FILEIO LIBRARY MAKES EXTENSIVE USE OF THE MACRO,
|
|
** AND CAN BE REFERRED TO FOR PLENTLY OF EXAMPLES.
|
|
*
|
|
* THE GRET MACRO - GET RETURN
|
|
*
|
|
** THE GRET MACRO STANDS FOR "GET RETURN," AND
|
|
** SIMPLY COPIES THE CONTENTS OF THE RETURN
|
|
** MEMORY BLOCK TO ANOTHER MEMORY BLOCK. THE
|
|
** LENGTH OF THIS BLOCK IS DETERMINED BY THE
|
|
** RETLEN VARIABLE, WHICH LOGICALLY PRECEDES THE
|
|
** RETURN BLOCK AS A BYTE. AS SUCH, NOTE THAT
|
|
** RETURN CAN ONLY HOLD UP TO 255 BYTES, AND
|
|
** THUS THIS IS THE MAXIMUM TO BE TRANSFERRED.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$11
|
|
** STA RETURN
|
|
** LDA #$12
|
|
** STA RETURN+1
|
|
** LDA #$13
|
|
** STA RETURN+2
|
|
** LDA #3
|
|
** STA RETLEN
|
|
** GRET #$300
|
|
** _PRN "THE 3 BYTES PUT IN RETURN ARE NOW AT $300:"
|
|
** DUMP #$300;#3
|
|
** _WAIT
|
|
** _"PRN " ",8D8D
|
|
_PRN "THE GRET MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDA #$11
|
|
STA RETURN
|
|
LDA #$12
|
|
STA RETURN+1
|
|
LDA #$13
|
|
STA RETURN+2
|
|
LDA #3
|
|
STA RETLEN
|
|
GRET #$300
|
|
_PRN "THE 4 BYTES PUT IN RETURN ARE NOW AT $300"
|
|
DUMP #$300;#3
|
|
_WAIT
|
|
_PRN " ",8D8D
|
|
*
|
|
* THE MFILL MACRO - MEMORY FILL
|
|
*
|
|
** THE MFILL MACRO FILLS A BLOCK OF MEMORY
|
|
** FROM A GIVEN STARTING ADDRESS AND A LENGTH
|
|
** IN BYTES WITH THE SPECIFIED FILL VALUE.
|
|
** NOTE THAT OVERFLOW ISSUES ARE TAKEN INTO
|
|
** CONSIDERATION, AND THAT DETERMINES HOW THE
|
|
** MEMORY LOCATIONS ARE READ AND FILLED (BACKWARDS
|
|
** OR FORWARDS).
|
|
*
|
|
** USAGE:
|
|
*
|
|
** MFILL #$300;#$08;#$FF
|
|
** _PRN "$300-$308 = "
|
|
** DUMP #$300;#$08
|
|
** _WAIT
|
|
** _PRN " ",8D8D
|
|
*
|
|
_PRN "THE MFILL MACRO",8D
|
|
_PRN "===============",8D8D
|
|
MFILL #$300;#$08;#$FF
|
|
_PRN "$300-$308 =",8D8D
|
|
DUMP #$300;#$08
|
|
_WAIT
|
|
_PRN " ",8D8D
|
|
*
|
|
* THE MOV MACRO - MOVE MEMORY
|
|
*
|
|
********************************
|
|
********************************
|
|
********************************
|
|
*
|
|
* THE MOVB MACRO - MOVE MEMORY BLOCKS
|
|
*
|
|
** THE MOVB MACRO, WHICH STANDS FOR MOVE
|
|
** BLOCK, DOES EXACTLY WHAT IT SAYS: IT MOVES
|
|
** A BLOCK OF MEMORY TO ANOTHER BLOCK OF
|
|
** MEMORY, THOUGH THIS SHUOLD BE SEEN MORE AS
|
|
** A COPY, SINCE THE ORIGINAL DATA IS STILL
|
|
** IN PLACE AFTER CALLING THE MACRO. NOTE THAT
|
|
** NO CHECKS FOR OVERLAP ARE MADE, SO ERROR
|
|
** CONTROL IS IN THAT RESPECT IS LEFT UP TO
|
|
** THE END PROGRAMMER.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$AA
|
|
** STA $300
|
|
** LDA #$BB
|
|
** STA $301
|
|
** LDA #$CC
|
|
** STA $302
|
|
** MOVB #$300;#$310;#3
|
|
** _PRN "$310-$312 NOW = $300-$302"
|
|
** DUMP #$310;#3
|
|
** _WAIT
|
|
** _PRN " ",8D8D
|
|
*
|
|
_PRN "THE MOVB MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDA #$AA
|
|
STA $300
|
|
LDA #$BB
|
|
STA $301
|
|
LDA #$CC
|
|
STA $302
|
|
MOVB #$300;#$310;#3
|
|
_PRN "$310-$312 NOW = $300-$302"
|
|
DUMP #$310;#3
|
|
_WAIT
|
|
_PRN " ",8D8D
|
|
*
|
|
* THE MSWAP MACRO - SWAP MEMORY BLOCKS
|
|
*
|
|
** THE MSWAP MACRO SWAPS THE VALUES HELD IN TWO
|
|
** BLOCKS OF MEMORY. NOTE THAT NO OVERFLOW CHECKING
|
|
** IS DONE, SO THE END PROGRAMMER HAS TO BE CAREFUL
|
|
** WHEN USING THE MACRO.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$66
|
|
** STA $300
|
|
** LDA #$77
|
|
** STA $301
|
|
** LDA #$88
|
|
** STA $302
|
|
** MSWAP #$300;#$310;#4
|
|
** DUMP #$300;#3
|
|
** DUMP #$310;#3
|
|
** _PRN " ",8D8D
|
|
** _PRN "$300-$302 HAS BEEN SWAPPED WITH $310-$312",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE MSWAP MACRO",8D
|
|
_PRN "===============",8D8D
|
|
LDA #$66
|
|
STA $300
|
|
LDA #$77
|
|
STA $301
|
|
LDA #$88
|
|
STA $302
|
|
MSWAP #$300;#$310;#4
|
|
DUMP #$300;#3
|
|
DUMP #$310;#3
|
|
_PRN " ",8D8D
|
|
_PRN "$300-$302 WAS SWAPPED WITH $310-312.",8D8D
|
|
_WAIT
|
|
*
|
|
* THE PEEK MACRO - PEEK MEMORY
|
|
*
|
|
** THE PEEK MACRO EMULATES THE PEEK INSTRUCTION
|
|
** IN APPLESOFT, KINDA. MOSTLY, IT JUST LOADS
|
|
** A VELUE FROM MEMORY INTO .A. HOWEVER, MORE
|
|
** FUNCTIONALITY MAY BE ADDED TO THIS MACRO IN
|
|
** THE FUTURE IN ORDER TO DIFFERENTIATE IT FROM
|
|
** SIMPLY LDA ##.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$EE
|
|
** STA $300
|
|
** PEEK #$300
|
|
** STA RETURN
|
|
** _PRN ".A NOW HOLDS:",8D8D
|
|
** DUMP #RETURN;#1
|
|
** _PRN " ",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE PEEK MACRO",8D
|
|
_PRN "==============",8D8D
|
|
LDA #$EE
|
|
STA $300
|
|
PEEK #$300
|
|
STA RETURN
|
|
_PRN ".A NOW HOLDS:",8D8D
|
|
DUMP #RETURN;#1
|
|
_PRN " ",8D8D
|
|
_WAIT
|
|
*
|
|
* THE POKE MACRO - POKE MEMORY
|
|
*
|
|
** LIKE THE PEEK MACRO, THE POKE MACRO SIMPLY
|
|
** BEHAVES LIKE THE APPLESOFT EQUIVALENT, PUTTING
|
|
** THE FIRST PARAMETER VALUE INTO THE SECOND PARAMETER'S
|
|
** ADDRESSS. LIKE WITH PEEK, MORE MAY BE ADDED TO THE
|
|
** MACRO'S FUNCTIONALITY IN THE FUTURE.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** POKE #$99;#$300
|
|
** _PRN "$300 CONTAINS:"
|
|
** DUMP #$300;#1
|
|
** _PRN " ",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE POKE MACRO",8D
|
|
_PRN "==============",8D8D
|
|
POKE #$99;#$300
|
|
_PRN "$300 CONTAINS:"
|
|
DUMP #$300;#1
|
|
_PRN " ",8D8D
|
|
_WAIT
|
|
*
|
|
*``````````````````````````````*
|
|
* THE WEIRDOS: CLRHI,DELAY AND *
|
|
* GBIT 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.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** LDA #$FF
|
|
** CLRHI
|
|
** STA $300
|
|
** DUMP #$300;#1
|
|
** _PRN "^^ THE HIGH NIBBLE WAS CLEARED"
|
|
** _PRN " ",8D8D
|
|
** _WAIT
|
|
*
|
|
_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 DELAY MACRO - DELAY
|
|
*
|
|
** THE DELAY MACRO SIMPLY DELAYS RXECUTION
|
|
** FOR A NUMBER OF MILLISECONDS. THIS ACCEPTS
|
|
** A BYTE VALUE, SO IF YOU WANT A SINGLE SECOND
|
|
** OF DELAY, YOU NEED TO RUN THE MACRO FOR TIMES
|
|
** AS SUCH:
|
|
*
|
|
** USAGE:
|
|
*
|
|
** DELAY #250
|
|
** DELAY #250
|
|
** DELAY #250
|
|
** DELAY #250
|
|
** _PRN "DONE!",8D8D
|
|
** _WAIT
|
|
*
|
|
_PRN "THE DELAY MACRO",8D
|
|
_PRN "===============",8D8D
|
|
DELAY #250
|
|
DELAY #250
|
|
DELAY #250
|
|
DELAY #250
|
|
_PRN "DONE!",8D8D
|
|
_WAIT
|
|
*
|
|
* THE GBIT MACRO - GET BIT VALUE
|
|
*
|
|
** THE GBIT MACRO RETURNS THE BIT VALUE
|
|
** OF THE GIVEN POSITION IN THE PARAMETER.
|
|
*
|
|
** USAGE:
|
|
*
|
|
** GBIT #0;#%00100101
|
|
** STA $300
|
|
** DUMP #$300;#1
|
|
** _WAIT
|
|
*
|
|
_PRN "THE GBIT MACRO",8D
|
|
_PRN "==============",8D8D
|
|
GBIT #0;%00100101
|
|
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.
|
|
*
|
|
* CALL - JSR EQUIVALENT
|
|
* CPL - INVERTS BITS IN .A
|
|
* JP - JMP EQUIVALENT
|
|
* LD - MOV VALUE FROM SRC TO DEST
|
|
* POP - PULL FROM STACK
|
|
* RET - RTS EQUIVALENT
|
|
* 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](./0.0 Title to TOC)
|
|
[Next: Detailed Reference: Disk 2: Standard Input and Output]()
|
|
|
|
|
|
|
|
|