SIG/M VOLUME 6 6502 SIMULATOR - REFERENCE DR. DOBBS 8/80 6502 ZAPPLE MONITOR SIZE NAME CONTENTS -CATALOG.006 CONTENTS OF SIG/M VOL. 6 ABSTRACT.006 DESCRIPTION OF EACH MODULE IN THE 6502 SIMULATOR 6.1 11K ZILASM.COM 6502 SIMULATOR 6.2 5K ZX65.COM 6.3 98K ZX65R.PRN 6.4 39K ZX65R.ZSM 6.5 15K ZXART.DOC 6.6 9K ZXHINTS.DOC 6.7 23K ZXLDR.PRN 6.8 7K ZXLDR.ZSM 6.9 1K ZXTAB1.DOC 6.10 1K ZXTAB2.DOC 6.11 1K ZXTAB3.DOC 6.12 1K ZXTAB4.DOC 6.13 2K CONCOD.LIB CONTROL CODES FOR 6502 ZAPPLE 6.14 1K ZAPMON.MOS ZAPPLE MONITOR FOR 6502 6.15 5K ZAPMON.HEX 6.16 11K ZAP3.INS 6.17 3K ZAP5.INS 6.18 3K ZAP1.INS This disk contains the following files, as described briefly here: 1: ZXART.DOC This is the original text of the article which app- eared in the August, 1980 edition of Dr. Dobb's Journal. 2: ZXTABx.DOC These tables are also part of the text for the ar- ticle. ** NOTE: Where addresses appear in these tables, they pertain to a 32K byte system with standard 512 byte CBIOS. For any other size system loaded just below CBIOS as described, the first two digits of these addresses will be {CBIOS page minus four}. I.E. for standard 32K, CBIOS page is 7EH. Subtract four and the result is 7AH. 3: ZXHINTS.DOC A little additional useful information on loading and running ZX65. 4: ZX65R.ZSM The Z80 source code for the interpreter. Version 2.1 is relocatable and must be linked with ZXLDR following assembly. 5: ZXLDR.ZSM Source code for the system 'booter.' CP/M loads ZX65R normally into the bottom of the TPA at 100H. ZXLDR finds CBIOS, calculates and inserts the relocation offsets, and then boots ZX65 up to high memory and jumps to it. Version 2.1 must be lin- ked with ZX65R following assembly for this to work. 6: ZX65R/XX.PRN and ZXLDR/XX.PRN This is the listing output of the assembler, showing system memory locations PRIOR TO RELOCATION. 7: ZX65.COM Getting down to business...you should be able to run this file immediately simply by typing 'ZX65' to the CP/M prompt. ZX65: Simulating a Micro Page 1 Having logged many hours of software development work on my Z80- based system, I recently decided that I was up to a particular challenge: using my system as a development tool for a totally different microprocessor; specifically, the 6502. This decision, I must confess, was not altogether based on a burning lust for knowledge, but rather on a more practical re- quirement: I had become involved in the development of a 6502-based device, and had no ready access to a 6502 development system. I decided that a sim- ple cross-assembler wasn't good enough, and set out to write a 6502-to-Z80 object code translator/simulator/interpreter. The program described here, which I have dubbed ZX65, is the result of my efforts to generate a software package to perform this task. It is useful not only for the intended purpose of software development, but also for the rewarding experience of exploring a whole new field of good soft- ware written for a processor other than one's own. ZX65 operates as an interpretive simulator; that is, a simulated 6502 CPU is maintained in system memory, and each encountered 6502 instru- ction is decoded and processed interpreter-fashion to properly act upon the simulated CPU registers. The entire package resides in just over 4K Bytes of high system memory, leaving the remaining portion, including the all-im- portant (for 6502) base page, free for program and data storage. ZX65: Simulating a Micro Page 2 Note that CP/M* serves only as a loader and is not used thereafter. In fact, while the program loads normally into CP/M's Transient Program Area, it imm- ediately relocates itself into high memory, overlaying both the CCP and the BDOS portions of CP/M. The user I/O linkages (CBIOS), however, remain intact and are used by the package to perform disk and console functions. The func- tions which must be provided by the system are a subset of those required by CP/M and should thus be readily available. These functions are all accessed via a jump table within the interpreter and thus can be readily changed to accomodate different system requirements (see table 1). Parameters must be passed to the drivers in register "C" (register pair "BC" for item six) and data, if any, is returned in the accumulator. ZX65 is referred to as a package since it includes three distinct function groups. The first and largest is the interpreter/simulator itself. The second group is an elementary monitor providing the functions of memory examine and modify. The third group consists of a self contained, elementary disk operating system (DOS). (Note: ZX65 files are not CP/M compatible.) The normal configuration assumes a two-drive system, with the system diskette on drive A, and all internal disk access routed to drive B. ZX65: Simulating a Micro Page 3 (Modification for a single drive system could easily be done, requiring the user to switch diskettes after the package is loaded.) The package is writ- ten in standard Zilog/Mostek source code and is quite heavily commented. Much use is made of the Z80's special instructions and index registers, and oper- ation on an 8080 is definitely not possible without extensive modification. Although the commented listing should guide you through the program's opera- tion without too much trouble, a brief description of the interpreter portion is in order. A study of the small but powerful instruction set of the 6502 will reveal certain patterns in the op codes, as also occurs with most other processors. The eight "BRANCH ON CONDITION" codes, for example, all have zero as a low-order nybble, while the high-order portion is always an odd value. Another pattern, not so obvious, is that every instruction having an odd- valued op code uses multiple addressing modes. These and other such patterns are exploited by the program. Even so, much of the code is devoted solely to decoding the op code and transferring control to the proper instruction proc- essor. The code beginning at "STEP" (line 241) performs the initial decoding, and fully decodes certain instructions such as "RTI", "JMP", and a few others. In most cases, decoding proceeds to the point where the instruction is known to be part of a group, such as the branch group described above. In these cases, look-up tables are used, but not always in the same way. ZX65: Simulating a Micro Page 4 The branch group and the flag set/reset group, for example, derive mask patterns from the tables which are used for testing, setting, or resetting the condition flags. In the case of multiple addressing mode instructions, however, the tables serve an entirely different purpose. The table contents are, in these instances, decoded into addresses for indirect jumps; first to the addressing mode processors, and then to the actual instruction processors. Each processor performs its instruction as required. The "ADC" instruction, as an example, causes a byte of data to be fetched from memory or from the object code, depending on the addressing mode. After setting the Z80 carry flag to match the simulated 6502 carry flag, the data byte is added to the value currently in the simulated accumulator, and the result is stored there. The simulated carry flag is then updated to again match the Z80 carry, and the simulated sign, overflow, and zero flags are set or reset according to the re- sults in the accumulator. (In this case, the overflow flag is set or reset according to a logical "OR" of bits six and seven.) Considerable care has been given to treatment of the flags, since Z80 flags and their 6502 name- sakes do not always have the same meaning. (Case in point: the 6502 carry flag assumes a logical "NOT" borrow meaning during subtract and compare op- erations, while the Z80 and most other common processors invert the carry flag internally during these operations so that it represents a "TRUE" bor- row. Other flag differences, for the most part, are more subtle.) ZX65: Simulating a Micro Page 5 Finally each individual processor is responsible for exiting with codes for the mnemonic, the addressing mode, and the number of bytes occupied by the instruction available for use by the "DISPLAY" routine (line 173) to in- dex into yet another set of tables and retrieve the ASCII messages for mnem- onic and addressing modes. Upon exiting the instruction processors, the val- ue stored in "NEXT PROGRAM COUNTER" is updated, and control is passed back to the COMMAND section where the operating mode in effect determines whether to display the updated CPU contents, and whether or not to continue execution with the next instruction. Several distinct operating modes are available, defined briefly as follows: SINGLE-STEP MODE: One instruction is decoded and executed. The console then displays the address of the instruction just executed (Current Program Counter), the decoded instruction (Mnemonic and Operand), the state of all CPU registers, and the address of the next instr- uction to be executed (Next Program Counter). Control is then returned to the user. MULTI-STEP TRACE MODE: The user specifies up to 255 instructions to be executed with the results displayed as above following each step. This occurs at the rate of approximately two instructions per sec- ond. Control is returned to the user after the required number of steps have been executed and displayed. RUN-TO-BREAKPOINT MODE: Instructions are executed without display up to and including the user-specified breakpoint. CPU registers are then displayed as above, and control is returned to the user. ZX65: Simulating a Micro Page 7 FREE-RUN MODE: Instructions are executed with no display and no break- points. The console keyboard, however, is monitored, and CPU con- tents are displayed as above and control is returned to the user at any point upon console input. The user may examine any or all CPU registers, including the Pro- gram Counter, at any time that he has control, without otherwise disturbing any operation on progress. The 6502 "BRK" instruction (software interrupt) is handled through a vector location, just as in a normal system. Optionally, however, the BRK instruction may be used (and is initialized) to return control to the user in the same manner as the breakpoint mode described above. The BRK instru- ction and breakpoint mode operate independently, and both may be used in the same program. Additionally, up to five different "user" subroutines may be called for transfer of control to Z80 routines (such as console I/O). These are invoked by executing either a "JMP" or "JSR" instruction to addresses 0000, 0001, 0002, 0003, and 0004 (HEX). Control is passed via a jump table located within the interpreter, which the user may load with Z80 "JP" ins- tructions to the desired subroutines. The subroutines must be terminated in the normal Z80 fashion by one of the "RET" instructions. Note that the first two entries in the table (JMP or JSR 0000, 0001 HEX) are initialized as console input and output via the 6502 accumulator. Three parameters may be passed to and from all user subroutines, as outlined in table 2. Table 3 lists the locations of the BRK vector and the user subroutine jump table. ZX65: Simulating a Micro Page 8 Although the interpreter is quite versatile and all 6502 instruc- tions are handled properly, there are some limitations to its use. Perhaps the most important of these is that, even in the "free-run" mode, execution is quite slow compared to an actual 6502 running at normal clock speeds. No attempt has been made to correlate execution times with normal 6502 clock periods. (This may, in fact, be impossible due to the way in which the sys- tem handles multiple addressing modes.) Therefore, programs incorporating software timing loops will not execute properly. Also, since the package was intended for software development, hardware interrupts are not supported, and only a limited amount of hardware interfacing can be performed through the "user" subroutine calls. Two program listings are included here. The first, called "ZXLD", is a short program whose only function is to boot the main program from the CP/M Transient Program Area to high memory. The second listing is the in- terpreter itself, assembled to reside from 6F00 to 7DFF (HEX) in a 32K sys- tem. Note that this leaves 512 bytes, normally containing CBIOS, unaffected. ZX65: Simulating a Micro Page 9 The program is not relecatable, so that in order to run in a different mem- ory configuration, it will be necessary to reassemble the source with a new value in the "SIZE EQU XX" statement. All other addresses are derived from this value. If the object code is to be manually loaded for the 32K system then "ZXLD" must be loaded first, starting at 0100 (HEX). Then start loading ZX65 at 0110 (HEX), continuing until the end. Finally, use the CP/M "SAVE" command (SAVE 15 ZX65.COM) to write the object code to disk. The package can now be invoked by answering "ZX65" to the CP/M prompt. If you are reassem- bling the package, then you should also reassemble the ZXLD routine (modified for your system) so that you have a .HEX file for each module. Then load the package using DDT as follows: DDT ZXLD.HEX IZX65.HEX Rdisp where "disp" is the load offset value required to load the object code from the .HEX file at a different address than that specified. (DDT will calcu- late "disp" for you if you type "H110,ssss", where "ssss" is the actual starting address of your program. DDT will respond with the sum and diff- erence of the two values, and the difference will be the required value "disp".) Finally, save the object code as directed above for manual loading. ZX65: Simulating a Micro Page 10 To run the program, place the disk with ZX65.COM on drive A (this disk should also have a copy of CP/M so that the system can be restarted, if necessary, without changing disks.) Place the ZX65 data disk on drive B. Start CP/M and invoke ZX65 by answering "ZX65" to the CP/M input prompt. The system will prompt you with a header and a ">" symbol. You are now in the command mode, and typing one of the command codes (see table 4) will cause the appropriate action to occur. Note that if you are using a disk not previously used for ZX65 on drive B, you will be unable to use the ZX65 DOS functions until you have initialized the disk. This is done simply by typing "I", at which point the system will ask you to verify this request with a "Y" or "N". The reason for this (note this well) is that the "I" command clears and initializes the directory on disk B. This is the equiv- alent of the CP/M command "ERA *.*" and must be used with caution. For those unwilling to type in a 4K byte program by hand, I will make the package available on a standard 8 inch IBM format, single side, single density diskette (Sorry, I can't handle any other format!) at a nom- inal charge of $15 to cover time and material. Write to me at the address given at the head of this article, and specify your system memory size. I will send a diskette containing both source and object files assembled to reside in high memory as described above, as well as a copy of ZXLD. ZX65: Simulating a Micro Page 11 A command summary, operating hints, and other general information will be in- cluded on the disk. Finally, although this program has been rather exhaustively tested with a variety of 6502 software (both simple and sophisticated) I cannot guarantee it bug-free. I have included no copyright notices of any kind, and I hope users will feel free to copy, add, delete, and improve at will. (I would like to hear of any major modifications...I may want to include them myself!) As stated at the outset, the package was developed as a side benefit of another project, not as a money maker, and if anyone else finds it useful, so much the better. -RMK Acknowledgements: *CP/M is a registered trademark of Digital Research, Pacific Grove, CA. While not specifically mentioned in the text, I have relied heavily on the information contained in the book "6502 Assembly Language Programming" by Lance A. Leventhal (Osborne/McGraw Hill Inc., 1979) ***** Notes on ZX65R v 2.1 and ZXLDR v 2.1 9/80 RMK The ZX65R program and source listings on this disk represent a substantial improvement over those published in DDJ #47. The 'R' in the program names means 'relocatable', and that means that the same ZX65 .COM file will now run on any size CP/M machine (except Heath/Zenith, TRS80, and any others using odd memory configurations). This has been accomplished mainly through a new loader, ZXLDR. This loader contains a complete relocation map for ZX65R. It finds your CBIOS by examining the warm start vector at 0001H. It then insinuates ZX65 just below CBIOS, after first calculating and inserting new values for all non-relocatable operands in the 'prototype' ZX65R code. The loader and the main program appear as two separate source modules which have been concatenated using a linking loader, resulting in the run-able ZX65.COM file. To run the program, transfer ZX65.COM onto a CP/M system disk using PIP. Boot up this disk in drive A, and place a blank disk that you want to use for 6502 programs and data in drive B. Type 'ZX65'. Drive B will operate briefly and the ZX65 prompt will appear on the console. You are now in ZX65's command mode. Refer to the Dobb's article or file ZXTAB1.DOC for a command summary. If you compare the source code for ZX65R with that published in DDJ, you will notice what appear to be substantial differences in the listing. The most obvious change is in the header comments and the 'eq- uates' which set up memory locations for the system. Even though this looks quite a bit different, it is for the benefit of relocatability and has no effect on the operation of the program once it is loaded. If you will look a little further and compare the actual instructions, you will see that they are mostly unchanged. There are two exceptions. The first involves the code from line 339 to line 428. These are the instruction processors for JMP, JSR, RTS, and RTI, and the new code corrects for im- proper stack handling in the original version. The second change is down near the end of the code in the console I/O handler. A patch there accom- odates a few BIOS console output routines which do not return with the output character still in the accumulator. Notice that the memory locations for the USER subroutine link- ages shown in Table 3 apply only after loading is complete. To insert your own subroutine addresses, the best method is to load ZX65.COM under DDT. You will then find the USER jump table at the following locations: USR0 0FCCH * * The first two locations are USR1 0FCFH * initialized to console I/O and USR2 0FD2H will be relocated during load- USR3 0FD5H ing. The last three will not USR4 0FD8H be relocated. You may also wish to change the 16-bit 'BRK' vector: BRK VECTOR 0FDBH Not relocated during loading. Once you have inserted the desired changes under DDT, you may either ex- ecute the program directly by typing G100, or save it to disk following warm start with the command 'SAVE 20 ZX65.COM'. CP/M COMPATABILITY: As of this writing, I have not yet succum- bed to Digital Research's efforts to get me to buy the new CP/M 2. From what I have been able to figure out, it offers very little to me in the way of advantages. Suffice it to say that ZX65 was written and debugged using V 1.4, and I cannot say whether or not it will run under any other version. (My guess is that it will work with V 1.3, but not v 2.X.) The main reason for any incompatibility will probably have to do with the fact that my self-contained mini DOS operates the disks and console dir- ectly through CBIOS rather than by using calls to BDOS which is the more conventional method. (There is a good reason for this madness which I will explain shortly.) In any event, since you have spent your hard- earned nickels for this copy, I suspect that you are resourceful enough to overcome such problems and implement any necessary patches. If you do come up with a running version patched for CP/M 2 or for some other disk format (i.e. North Star etc.), how about submitting it for possible publication in Dobb's? (No, I'm not on their payroll, but they are re- ceptive to this type of material and lots of 'hard core' dedicated computerists read their mag.) SOME NITTY-GRITTY: As promised, here is the explanation of why I have interfaced through CBIOS rather than using BDOS: If you have done any work at all with the 6502 processor, you will recall that page zero of memory (0000H to 00FFH) is sacred to it. There are short forms of many instructions for use on page zero, and the very powerful indirect indexing modes always use page zero. Now ZX65 by design uses no memory mapping at all...that is, if your 6502 program is ORG'd at 2000H, that is exactly where you will load it. (This is in contrast to at least one TRS80-based simulator that I know of.) Having gone this far, it is only reasonable to expect that page zero will be totally available to the 6502 program. As you have probably realized by now, the catch here is that BDOS also likes page zero, and uses large chunks of it during disk accesses. With one possible limitation (which I will discuss in a bit) ZX65 totally removes the host system from page zero...even the vectors at the bottom of memory need not be saved. ZX65 is, in fact, confined to the top 1100H bytes of user memory (more if your CBIOS is longer than 512 bytes), and ALL space below that is available for 6502 programs and data. The one possible conflict that I mentioned is system-dependent, and may or may not apply in your case. CP/M V 1.4 has a reserved 'scr- atch' area of 16 bytes located from 0040H to 004FH. Some disk controllers use this area for temporary data storage during all disk transfers, whe- ther initiated by CP/M or not. If this is true of your controller, then you have very little choice but to accept the fact that this portion of page zero will be messed up whenever you do a disk access from ZX65. There is still one alternative (I know because I did it!): you may be able to move this scratch area out of page zero into high memory. Even if you have this conflict, 6502 programs that use page zero only for temporary data storage will probably run normally unless the 6502 pro- gram itself accesses the disk. (Yes, it is possible, using the 'system' subroutine calls.) ZXDOS COMMENTS: Having destroyed BDOS as described above, I was obliged to include an embedded disk operating system in ZX65. Now, ZXDOS will never win any awards for versatility, but it is, I think, adequate for the application. All internal disk access is confined to drive B (a two drive system is assumed). ZXDOS files are not CP/M compatible. A dir- ectory is maintained on track zero, and the remainder of the disk is available for program/data storage. Each directory entry is 16 bytes long, and contains the file name (format same as CP/M but file type has no spe- cial meaning), the load address, the disk location, the number of sectors, and an active/dead flag byte. During a directory display or search, the directory is paged through a buffer within ZX65 one sector at a time, continuing until an 'end of directory' mark is located. Files are stored sequentially and on sequential sectors (no interleaving). A new file will normally be appended at the tail end of existing files, unless the entire file will fit into the space allocated for a dead file. Thus a rudimentary form of dynamic disk space allocation is used, but a full disk will still contain quite a few empty slots. Killing a file does not actually erase anything, but simply resets the active status flag in the directory for that entry. The 'initialize' command writes a properly formatted empty directory to the disk. Finally, it is be possible to use ZX65 with only one drive. To implement this, load ZX65.COM using DDT, and change the value at address 484H from '01' to '00'. Save the modified code back to a system disk following warm start using a 'SAVE 20 ZX65.COM' command. To operate in this mode, first start ZX65, then immediately remove the system disk and insert the ZX65 data disk. From this point operation will be the same. This works because once loaded, ZX65 does not itself require any further disk access. CONCLUSION: Have fun with ZX65! -RMK ZX65: Simulating a Micro Page T1 *** TABLE 1 *** Functions which ZX65 requires of the host system, and their locations within the interpreter. ----------------------------------------------------------- FUNCTION ADDRESS -------- ------- 1. Test console input status --------------------- 7A2EH 2. Get single character from console ------------- 7A2BH 3. Send single character to console -------------- 7A28H 4. Select disk drive (A or B) -------------------- 7A34H 5. Home R/W head of selected drive --------------- 7A31H 6. Set disk transfer buffer address -------------- 7A3DH 7. Set sector number for next disk access -------- 7A37H 8. Set track number for next disk access --------- 7A3AH 9. Read one sector from selected drive ----------- 7A40H 10. Write one sector to selected drive ------------ 7A43H ZX65: Simulating a Micro Page T2 *** TABLE 2 *** 6502/Z80 parameter passing to/from user subroutines. ---------------------------------------------------- 6502 Register Passed as Z80 Register ------------- ------------ Accumulator ------------------- Accumulator X Index ----------------------- B Register Y Index ----------------------- C Register ZX65: Simulating a Micro Page T3 *** TABLE 3 *** Locations for "BRK" vector and user subroutine table. ----------------------------------------------------- Description Address of vector/jump ----------- ---------------------- BRK Vector ------------ 7A55H User 0 ---------------- 7A46H User 1 ---------------- 7A49H User 2 ---------------- 7A4CH User 3 ---------------- 7A4FH User 4 ---------------- 7A52H ZX65: Simulating a Micro Page T4 *** TABLE 4 *** A command summary for ZX65 (in alphabetical order) -------------------------------------------------- COMMAND FUNCTION ------- -------- C ------------- CPU register display & modify D ------------- Directory display of ZX65 files on drive B Format: FILNAM.TYP LOAD ADDR # RECORDS E ------------- Examine block of memory. System will prompt for starting address and number of bytes. G ------------- Go execute 6502 program per Current Program Counter. I ------------- Initialize a fresh disk on drive B. (Must be for- matted.) System will prompt for verification. K ------------- Kill a ZX65 file on drive B. Similar to CP/M ERA. System will prompt for file name. L ------------- Load a ZX65 file from drive B. System will prompt for file name. M ------------- Memory substitution/modification. Use to view seq- uential memory locations and update if desired. System will prompt for address. Mode will then continue in effect until a '.' is typed. R ------------- Load and run a ZX65 file from drive B, starting at the address specified in the directory. (Must be an executable 6502 program.) System will prompt for file name. S ------------- Save a ZX65 file on drive B. System will prompt for file name, starting address, and number of bytes. T ------------- Trace several 6502 instructions with display following each step. System will prompt for desired number of steps. 'SPACE' ------- Single-step command. System will execute one 6502 instruction, display the results, and stop.