ASMLIB Documentation -------------------- 29/11/83 -------- A library of CP/M-80 utilities and routines for assembly language programmers. T A B L E O F C O N T E N T S --------------------------------- 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 1. 1. Choosing a Library . . . . . . . . . . . . . . . . . . . . 1. 1. 1. Disadvantages of Simple Programming . . . . . . 1. 1. 2. Library Advantages . . . . . . . . . . . . . . . 1. 2. Choosing Assembly Language . . . . . . . . . . . . . . . . 1. 3. ASMLIB Design Philosophy . . . . . . . . . . . . . . . . . 1. 4. Future Expansions . . . . . . . . . . . . . . . . . . . . . 1. 5. ASMLIB Code Overheads . . . . . . . . . . . . . . . . . . . 1. 6. Updates . . . . . . . . . . . . . . . . . . . . . . . . . . 1. 7. ASMLIB Functions Summary . . . . . . . . . . . . . . . . . 2. ASMLIB Commands in Detail . . . . . . . . . . . . . . . . . . . . 2. 1. Parameter Conventions . . . . . . . . . . . . . . . . . . . 2. 2. Miscelaneous Functions . . . . . . . . . . . . . . . . . . 2. 2. 1. PROLOG - Start of a program . . . . . . . . . . . . 2. 2. 2. QUIT - End of a program . . . . . . . . . . . . . . 2. 2. 3. VERSION - Return version number . . . . . . . . . . 2. 2. 4. CLKRD - Read real time clock . . . . . . . . . . . 2. 2. 5. CLKWR - Write to real time clock . . . . . . . . . 2. 2. 6. CHKRNG - Range check the accumulator . . . . . . . 2. 2. 7. CHKTBL - Table driven range checking . . . . . . . 2. 2. 8. FORMIN - Formatted display and input . . . . . . . 2. 2. 9. MON - Stand alone monitor . . . . . . . . . . . . . 2. 2.10. ATODIN - Analogue to digital input . . . . . . . . 2. 2.11. PORTSET - Port initialization . . . . . . . . . . . 2. 2.12. DELAY - Perform accurate time delay . . . . . . . . 2. 3. Cyclic Redundancy Check . . . . . . . . . . . . . . . . . . 2. 3. 1. CLRCRC - Clear current CRC . . . . . . . . . . . . 2. 3. 2. ADDCRC - Adding a byte to the current CRC . . . . . 2. 3. 3. GETCRC - Return the current CRC . . . . . . . . . . 2. 4. Random Number Routines . . . . . . . . . . . . . . . . . . 2. 4. 1. RANDINIT - Initialize random number seed . . . . . 2. 4. 2. RAND8 - Return a 8 bit random number . . . . . . . 2. 4. 3. RAND16 - Return a 16 bit random number . . . . . . 2. 4. 4. RANDP16 - Return positive 16 bit random number . . 2. 5. Memory Test Routines . . . . . . . . . . . . . . . . . . . 2. 5. 1. RAMBPT - Barber pole memory test . . . . . . . . . 2. 5. 2. RAMWBT - Walking bit memory test . . . . . . . . . 2. 6. Screen Functions . . . . . . . . . . . . . . . . . . . . . 2. 6. 1. TNAME - Return terminal name string address . . . . 2. 6. 2. PSTRING - Print a string of text . . . . . . . . . 2. 6. 3. INLINE - Inline text string printing . . . . . . . 2. 6. 4. XYPSTRING - Cursor address string printing . . . . 2. 6. 5. XYINLINE - Cursor addressed inline printing . . . . 2. 6. 6. PCOUNT - Counted character string printing . . . . 2. 6. 7. PMENU - Menu display routine . . . . . . . . . . . 2. 6. 8. PSTR - Repeated character printing . . . . . . . . 2. 6. 9. CLEAR - Clear the console screen . . . . . . . . . 2. 6.10. CLEOL - Clear to end of line . . . . . . . . . . . 2. 6.11. CLEOP - Clear to end of page . . . . . . . . . . . 2. 6.12. BELL - Ring console bell . . . . . . . . . . . . . 2. 6.13. CURON - Enable the cursor . . . . . . . . . . . . . 2. 6.14. CUROFF - Disable the cursor . . . . . . . . . . . . 2. 6.15. CURSOR - Position the cursor . . . . . . . . . . . 2. 6.16. SETXY - Indirect cursor position . . . . . . . . . 2. 6.17. CRLF - Print a CR/LF pair . . . . . . . . . . . . . 2. 6.18. ECHOLST - Echo console to printer . . . . . . . . . 2. 6.19. LISTOUT - Re-direct console to printer . . . . . . 2. 6.20. CONSOUT - Restore console output (default) . . . . 2. 6.21. USERIO - Patch user console I/O drivers . . . . . . 2. 6.22. DISPATCH - Print a character . . . . . . . . . . . 2. 6.23. COE - Directly print a character . . . . . . . . . 2. 6.24. LOE - Send a character to the printer . . . . . . . 2. 6.25. LST - Return printer output status . . . . . . . . 2. 6.26. FLUSHLOE - Flush line printer buffer . . . . . . . 2. 6.27. CIE - Read a console character . . . . . . . . . . 2. 6.28. CST - Return the console status . . . . . . . . . . 2. 6.29. CBUFF - Read the console into a buffer . . . . . . 2. 6.30. SETATT - Set console visual attributes . . . . . . 2. 7. Numeric Printing Functions . . . . . . . . . . . . . . . . 2. 7. 1. LZB - Select leading zero blanking . . . . . . . . 2. 7. 2. BLZB - Right justified LZB . . . . . . . . . . . . 2. 7. 3. CLZB - Character fill LZB . . . . . . . . . . . . . 2. 7. 4. NOLZB - Disable LZB . . . . . . . . . . . . . . . . 2. 7. 5. Sample LZB function output . . . . . . . . . . . . 2. 7. 6. PHACC - Print the accumulator as hex digits . . . . 2. 7. 7. PDACC - Print the accumulator as decimal digits . . 2. 7. 8. PHDE - Print DE as hex digits . . . . . . . . . . . 2. 7. 9. PDDE - Print DE as decimal digits . . . . . . . . . 2. 7.10. PSHDE - Print signed hex DE . . . . . . . . . . . . 2. 7.11. PSDDE - Print signed decimal DE . . . . . . . . . . 2. 8. Conversion Functions . . . . . . . . . . . . . . . . . . . 2. 9. Standard CP/M Console Buffer . . . . . . . . . . . . . . . 2. 9. 1. ATOASC - Convert accumulator to ascii . . . . . . . 2. 9. 2. ATOHEX - Convert accumulator to hex . . . . . . . . 2. 9. 3. ASCBCD - Convert ascii bcd memory to hex . . . . . 2. 9. 4. ASCHEX - Convert ascii memory to hex . . . . . . . 2. 9. 5. HEXASC - Convert hex to ascii . . . . . . . . . . . 2. 9. 6. HEXBCD - Convert hex to bcd . . . . . . . . . . . . 2. 9. 7. NIBASC - Convert A to ascii . . . . . . . . . . . . 2. 9. 8. IHHL - Input a hex number into HL . . . . . . . . . 2. 9. 9. IDHL - Input a decimal number into HL . . . . . . . 2. 9.10. CAPS - Capitalize the accumulator . . . . . . . . . 2. 9.11. CUPSBUF - Capitalize a console buffer . . . . . . . 2.10. Maths Routines . . . . . . . . . . . . . . . . . . . . . . 2.11. Signed numbers representation . . . . . . . . . . . . . . . 2.11. 1. MULDH - Multiply . . . . . . . . . . . . . . . . . 2.11. 2. DIVDH - Divide . . . . . . . . . . . . . . . . . . 2.11. 3. SQRT - Square root . . . . . . . . . . . . . . . . 2.11. 4. COMDH - Compare HL to DE . . . . . . . . . . . . . 2.11. 5. COMP2S - Compare 2's complement . . . . . . . . . . 2.11. 6. SGNADD - Signed add HL to DE . . . . . . . . . . . 2.11. 7. SGNSUB - Signed subtract HL from DE . . . . . . . . 2.12. Special String Routines . . . . . . . . . . . . . . . . . . 2.12. 1. CONSTR - Concatenate strings . . . . . . . . . . . 2.12. 2. CMPSTR - Compare two strings . . . . . . . . . . . 2.12. 3. POSSTR - Find a substring in a string . . . . . . . 2.12. 4. CPYSTR - Copy a substring from a string . . . . . . 2.12. 5. DELSTR - Delete a portion of a string . . . . . . . 2.12. 6. INSSTR - Insert a substring into a string . . . . . 2.12. 7. CAPSTR - Capitalize string . . . . . . . . . . . . 2.12. 8. PRNSTR - Print a standard character string . . . . 2.12. 9. BUFSTR - Transfer a buffer to a character string . 2.12.10. STRBUF - Transfer a string to a console buffer . . 2.12.11. ELBSTR - Eliminate leading blanks . . . . . . . . . 3. ASMLIB organization . . . . . . . . . . . . . . . . . . . . . . . 3. 1. ASMLIB module dependences . . . . . . . . . . . . . . . . . 4. Using ASMLIB routines . . . . . . . . . . . . . . . . . . . . . . 4. 1. Writing the assembly language programs . . . . . . . . . . 4. 2. Assembling the file . . . . . . . . . . . . . . . . . . . . 4. 3. Linking the file . . . . . . . . . . . . . . . . . . . . . 4. 3. 1. Using the LIB program . . . . . . . . . . . . . . . 5. Writing Assembler Programs . . . . . . . . . . . . . . . . . . . 5. 1. Block Structuring . . . . . . . . . . . . . . . . . . . . . 5. 2. Top Down Design . . . . . . . . . . . . . . . . . . . . . . 5. 3. Flowcharting . . . . . . . . . . . . . . . . . . . . . . . 5. 4. Pseudo Coding . . . . . . . . . . . . . . . . . . . . . . . 5. 5. Screen Model . . . . . . . . . . . . . . . . . . . . . . . 5. 6. In the Long Run . . . . . . . . . . . . . . . . . . . . . . 5. 7. Program Style Summary . . . . . . . . . . . . . . . . . . . 5. 8. Testing . . . . . . . . . . . . . . . . . . . . . . . . . . 5. 9. Debugging . . . . . . . . . . . . . . . . . . . . . . . . . 5.10. Bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Modifying the ASMLIB Library . . . . . . . . . . . . . . . . . . 6. 1. Adding Functions to ASMLIB . . . . . . . . . . . . . . . . 6. 2. Substituting a Module . . . . . . . . . . . . . . . . . . . 6. 3. Generating a new library . . . . . . . . . . . . . . . . . 6. 4. ASMLIB Source Code . . . . . . . . . . . . . . . . . . . . 6. 4. 1. Hidden Functions . . . . . . . . . . . . . . . . . 7. Different Hardware Consideration . . . . . . . . . . . . . . . . 7. 1. Data storage areas . . . . . . . . . . . . . . . . . . . . 7. 2. Running in ROM . . . . . . . . . . . . . . . . . . . . . . 7. 3. Non CP/M I/O . . . . . . . . . . . . . . . . . . . . . . . 7. 3. 1. Linking I/O Drivers . . . . . . . . . . . . . . . . 7. 4. Space vs Size . . . . . . . . . . . . . . . . . . . . . . . 8. I/O Routines and Modifications . . . . . . . . . . . . . . . . . 8. 1. The special I/O drivers . . . . . . . . . . . . . . . . . . 8. 1. 1. CPMIO . . . . . . . . . . . . . . . . . . . . . . . 8. 1. 2. SBC800IO . . . . . . . . . . . . . . . . . . . . . 8. 1. 3. CPMMPC . . . . . . . . . . . . . . . . . . . . . . 8. 1. 4. SBCMPC . . . . . . . . . . . . . . . . . . . . . . 8. 2. Different terminal support . . . . . . . . . . . . . . . . 9. ASMLIB Utilities . . . . . . . . . . . . . . . . . . . . . . . . 9. 1. SETUP.COM - An Initialization Program . . . . . . . . . . . 9. 1. 1. Edit terminal definition . . . . . . . . . . . . . 9. 1. 2. Kill a definition . . . . . . . . . . . . . . . . . 9. 1. 3. Replicate a definition . . . . . . . . . . . . . . 9. 1. 4. Display terminals on file . . . . . . . . . . . . . 9. 1. 5. Create a terminal definition . . . . . . . . . . . 9. 1. 6. Install a .COM file . . . . . . . . . . . . . . . . 9. 1. 7. Quit to CP/M . . . . . . . . . . . . . . . . . . . 9. 2. FP.COM - A file display and patch program . . . . . . . . . 9. 3. TESTALL.COM - A test program . . . . . . . . . . . . . . . 9. 4. HLP - A HELP Screen Handler . . . . . . . . . . . . . . . . 9. 4. 1. Using HLP . . . . . . . . . . . . . . . . . . . . . 9. 4. 2. Help file format . . . . . . . . . . . . . . . . . 9. 4. 3. HLP - Initialization Code . . . . . . . . . . . . . 9. 4. 4. HLP - Screen selection code . . . . . . . . . . . . 9. 4. 5. HLP - Error handlers . . . . . . . . . . . . . . . 10. The Distribution Disk . . . . . . . . . . . . . . . . . . . . . . 11. Monitor Command Summary . . . . . . . . . . . . . . . . . . . . . 12. References . . . . . . . . . . . . . . . . . . . . . . . . . . . Quick Start To use ASMLIB routines in a program of your own as soon as possible, without reading the rest of this manual, you can use the following steps. 1) Create your assembly language source file with an editor. 2) Look at the table of contents page and see what routines you want to use. 3) Declare the routines from ASMLIB as EXTRN in your program. Set up the re gisters as required by the routines. 4) Assemble your program with RMAC. 5) Link your program to ASMLIB using LINK. 6) Run the program. Two very simple programs are on the distribution disk called BUGS.ASM and TATT.ASM. These programs may be modified and looked at to see how easy it is to use ASMLIB routines to generate working programs in a hurry. 1. Introduction ***************** This is the manual to ASMLIB which is a library of functions or subroutines which have been written to ease the task of writing (Z-80 assembly language) programs. The programmer is urged to use routines from the library instead of going to the expense of writing the code so suit the application. In this manner the effort of re-creating or duplicating program segments is eliminated. This library aims at increasing the efficiency of assembly language programmers. ASMLIB is compatible with the Digital Research macro assembler and linkers and is distributed in '.REL' format though source code is available. To use ASMLIB only requires the programmer to assemble his source code file and link it to ASMLIB to utilize its functions. The time taken to try an application program is reduced when using ASMLIB since the assemble process does not have to assemble all the functions used by the program which are in the library. The link process does not impose much time penalty. ASMLIB also supports hardware from SME Systems so that interfacing time and effort is reduced. This support enables the user to write code which may be made quite hardware independent by changing sections of library code rather that altering numerous application programs. This manual will also give a taster to some of the more popular and better ways of writing better programs. Block structuring of programs is stressed with pseu do coding, flowcharting and top down design mentioned. After reading the pages on these methods the programmer can 'have a go' and then venture out into the bookshop to get a good reference work on the methods of choice. This is a manual on using some assembly language routines and therefore programming method is only touched on. The code used in this manual, the library and the disks of software that accom pany ASMLIB all use the 'MAC' or 'RMAC' compatible style of Z80 source code. This type of code is NOT standard Z80 code. MAC style code is used because the author far prefers 'MAC Z80' over true Z80, mainly for the following reasons. If this does not suit the reader then, tough. --O MAC and RMAC are preferred to all other assemblers --O MAC and RMAC were developed before true Z80 assemblers --O Far more code was/is available for MAC Z80 assemblers --O Facilities offered by MAC and RMAC are preferred --O All Z80 opcodes are accessible through the use of libraries --O Better support is available to users of MAC and RMAC 1.1. Choosing A Library ====================== A library has advantages over other methods of code generation. Some of the common disadvantages of other methods of software generation are given below. The reader should note that the writer of this has tried these other methods and that these pages are written from the heart. --O Easier to start assembler coding --O Greater confidence that a job can be completed to schedule --O Better hardware independence --O Faster development of working programs --O Better programs due to powerful functional blocks --O Easy updating or bug fixes of programs --O Structured programming more easily implemented --O Less disk space required than other methods --O Faster assemble and link times --O Easy modification / expansion / generation of libraries 1.1.1. Disadvantages of Simple Programming --------------------------------------------- Some of the common disadvantages of simple inline code production are laid out below. --O Larger disk files --O Repeated effort and debugging due to duplicated code --O Prone to errors due to individual repeated efforts --O Slower to assemble --O Easier to generate cumbersome programs with rambling code --O MACROS can be complicated and are slow to assemble --O Less ease of use and modification --O One bug fix does not fix all application programs MACROS are extremely powerful for writing application programs and would be the second choice to using a library of routines. Macro libraries suffer from a few simple disadvantages though. Since a macro library is in source code then it can become a large disk file. Secondly, the time required to assemble a file when using large macro libraries can increase dramatically. Disk space quickly becomes a problem. Another nasty feature of macro libraries is that they do quickly become very difficult to understand how they will expand and they also make the source and print files look messy. A point in case is the buggy macros that are distributed with CP/M. The reader should learn about macro programming since this is a very flexible powerful method of programming, but not the best. With text editors, it is very easy to have slabs of standard text which the programmer can quickly load into his new file. The first criticism of this is that the user probably has not designed the individual slabs of text for easy use in other programs so that getting the text slab going in the new program usually requires some effort. Text slabs suffer from the same disadvantages of macro programming with the time taken to assemble and the quickly diminishing disk space. Also another hidden problem is that since each program is 'stand-alone', the programmer must modify ALL files if a text slab is later found to be faulty. Tedious in the extreme and prone to flaws. 1.1.2. Library Advantages ---------------------------- The first advantage of a library of functions is that the user has a set of guidelines around which to base an application program. The user is forced into a a better style of work habit than would be possible with inline code genera tion. This style of approach is exactly what has made CP/M such a popular opera ting system. The user has a documented method for using powerful routines that help to produce an end result. A library is also more independent of hardware changes, the user only has to modify the I/O or some similar section of code to support new hardware. By ha ving separate libraries for each hardware situation, the user is able to link an application designed for one machine to run on another merely by linking in different libraries. By using the changing I/O library approach, a program may be emulated under CP/M then run on the target machine by re-linking to suit the new hardware. ASMLIB supports this with files for different I/O. Using a library encourages an upgrade path to newer microprocessors. Since as sembly language is the least transportable of all languages, it stands to reason that if a new processor is to be used that all required software must be re- written. With a library that has a good percentage of the code from ALL programs in it, the user only has to re-write the library code once which is a saving. Also the distributors of the library may be able to offer compatible libraries for the new processor thereby saving even more time. Hopefully you have bought the new processor based equipment from them. The library approach combines the best points of other methods. When using a library the programmer creates smaller source code files which are more easily understood. The library is far smaller than a macro library since it is in REL (assembled into relocatable) format rather than source code. The assembly of the source file is faster since the assembler used does not have any macro libraries to search nor perform any assembly time arithmetic. The user is still able to take advantage of macros to interface to the library, though this is not really necessary if the library has been well built and defined. In the event of a faulty routine in the library, the programmer merely fixes the bug then re-links all his programs to the corrected library. This does not take long and is almost fool proof. 1.2. Choosing Assembly Language ============================== The most usual reasons for assembly language coding can be summarized as below. --O Far faster than any compiled language --O Far more efficient usage of memory and resources --O Total control of the computer and all its resources --O Code is easily generated for ROM based applications --O Support of many specialized devices requires assembler coding --O Support for high level languages Assembly language is possibly the most difficult to master and the most time consuming to produce working programs. High level languages are far more trans portable than assembly language and require less time to generate working prog rams. This looks very damaging for assembly language, but reasons for using it do exist. Reading the back of this manual will show that there are good methods of organizing work habit that allow the programmer to improve program genera tion. Firstly, assembly language is FAST. The best optimized and complied program in high level language is probably 10 times slower than a slapped together assembly language program. The size of the resultant programs will differ greatly in size also. The compiled high level language will probably produce a program at least 5 times larger than the assembly program. For industrial and process control, assembler language is almost a must. Next to consider is the environment in which the program is to run. While it is possible to modify some compilers to generate rom-able code, it is very easy to generate assembly code to run in rom. ASMLIB has been written to allow romable code and this is supported by the Digital Research linker. One other consideration is that many things that are easy in assembler are very difficult to do in a high level language. Examples of this are interfacing to complex architectured integrated circuits or devices such track-balls, video controllers, interrupt controllers, dma controllers etc.... A good look at the routines in ASMLIB will show that they would require considerable effort in any high level language which would never produce the same speed or code efficiency. Some of the compiled languages (Pascal, C, PL/I) are now flexible enough to allow the user to either link assembly programs to them directly, or to include code inside the program and assemble it with an 'inline assembler'. This makes for an extremely powerful environment for generating hardware interfaces and optimizing speed critical portions of a program. Also of note is that as new chips are being developed it is usually far easier to support them from the high level language via an assembly language driver. You still need to know how to program in assembler! One of the hidden costs in assembly language programming is the time involved in program maintenance. It is not unusual for the code produced by one programmer to be extremely difficult for most other programmers to debug or modify at a later date . The same can be said for some of the less structured high level languages. The only way that this may be overcome is by proper program planning, structuring and careful documentation (ie PLANNING). This is a must for serious programmers as nothing is surer than the boss wanting a mod' to an existing program. Ever tried to modify a BASIC program that is a year old, with lots of gotos, written by someone else ???? Assembly language can be the same. The use of assembly language will never die out for the above reasons (and others). Some of the disadvantages of assembly programs can be overcome by care ful work habit and by using libraries of code to do the mundane leg work. Macro libraries are a help too. The non-transportability of assembly code can be eased a little by using 'caressing' programs which convert the source code to suit a new processor. This type of operation is also eased by staying with a manufac turer. An example of this is the Intel series of microprocessors that has semi- compatible source and object code so that one set of software may be modified to run on the next type of machine. The reader should also consider that inside every computer is a program written in assembler. This may be the system monitor, disk driver primitive I/O routines or whatever. It is unlikely that this type of program will be written by a high level language due to the constraints on size, speed and location. 1.3. ASMLIB Design Philosophy ============================= ASMLIB was written to make assembly language programming easier and faster. To this end the source code for many many programs was reviewed and common segments of code noted. Also noted was common omissions from programs that were not put in since the code required would have been difficult or painful to debug. The resulting list was then the object of much speculation and 'what-iffing'. Using the library also pointed out serious omissions of functions that would have been useful and made a program far more salable. This was quickly rectified. A need produced a function. Another major factor in the design was the consideration of what else was on the market or in public domain. Good ideas are good wherever they came from and code was modified to suit ASMLIB from these external sources. The last process in the selection of subroutines was the consultation of the people who may have been interested in a software product like this. The users were consulted and their preferences noted and considered. This input to the design of ASMLIB is considered to be the most important since to be a success it has to provide the required functions. It is also expected that this input to ASMLIB will never end since new products are always being developed and needs change. The actual writing of ASMLIB was undertaken by using as many tried and true routines as possible using all the source code files and the noted references as input for code generation. Many routines were written specifically for the task at hand, but these were kept to a minimum so as to keep debugging time to a minimum. One last factor in all software design that is sometimes forgotten is the cost involved in maintenance of a program. This has been considered as the routines were written and commented and it is expected that ASMLIB routines should be easy for the casual assembly language programmer to do modifications as needed. Commenting and flow descriptions have been included in all non trivial sections of the source code. For the above reasons, a few tricky routines were discarded since they were overly complex and difficult to understand. Simpler routines were substituted which it is expected that they will be easier to maintain and update. 1.4. Future Expansions ====================== The future of ASMLIB depends on the attitudes of its users as much as it depends on its creators. For this reason ALL reasonable comments, criticisms and ideas are usually welcomed, sometimes acted upon and always listened to. In general, if you can make a good argument for an improvement which still looks good the next day then you will probably get your way. At the time of writing the most likely area of expansion would have to be into disk accessing and graphics support routines. 1.5. ASMLIB Code Overheads ========================== ASMLIB is quite extensive and contains pieces of code that enable the user to easily do quite complex things in assembly language that otherwise is tedious at best. The user must realize at some stage that some sacrifices in code overhead need to be made due to the added features that are available. You are being re- paid for the overheads by increased efficiency in program development and far better programs. You would have to be pretty picky to be worried by the over heads anyway. The thing to remember also is that some of the functions are powerful and so they do chew up a little memory, there is no way out, you only get what you pay for. On some programs, the resultant executable disk files that use ASMLIB functions may easily be twice the size of files not using ASMLIB. The reason for this is that the programmer has used many functions from ASMLIB and ended up with a program that is far more powerful than would otherwise have been written. This is saying that by using ASMLIB the user is encouraged to write better programs because the code is immediately available for use. If you want small programs though, you only have to restrict yourself to the bare essentials and you get small programs. Since ASMLIB is a general purpose library there have been some assumptions made as to what will be used. The bottom line for the programmer is that the code produced by linking in ASMLIB will be somewhere between 0 and 50 percent larger than a hand optimized program. The 0 percent case would apply when every func tion from ASMLIB were used in a program and the 50 percent case when only a few were used. It should be noted that the routines used in ASMLIB are quite smart and the best available at the time with the previous noted restrictions taken into account. 1.6. Updates ============ Nothing is free, ASMLIB has been priced at a nominal low level so as to encour age its use. To legitimate purchasers of ASMLIB an update may be purchased (ex cluding manual) for the same cost as a CP/M user group disk. If you supply an improvement, bug fix or save the authors some effort then it will be supplied free. Postage must be included in any mail orders. Manuals for ASMLIB will be updated as extensions are done and features are added though it is not reason able to re-print a new manual for trivial additions. It is expected that stapled sheets at the back of this manual will contain any newly added functions that didn't make it into these pages. New manuals or spares cannot be supplied free, though the asking price is a bargain, considering the time and work that goes into a manual. 1.7. ASMLIB Functions Summary ============================= Miscellaneous Functions PROLOG Initialize ASMLIB and set up stack etc. QUIT Exit program back to CP/M directly. VERSION Return the version of ASMLIB. CLKRD Read real time clock to memory. CLKWR Set the real time clock from memory. CHKRNG Range checks that D <= A <= E . CHKTBL Range checks via a table of values and addresses. FORMIN Display a menu and read data into screen fields. MON A useful diagnostic monitor. ATODIN Read an analogue to digital channel. PORTSET Send a table of bytes to a digital port. DELAY Delay a number of milliseconds. CLRCRC Clear the current checksum ADDCRC Add the accumulator to the current checksum. GETCRC Return the current checksum. RANDINIT Initialize random number generator. RAND8 Return an 8 bit random number. RAND16 Return a 16 bit random number. RANDP16 Return a positive (15 bit) random number. RAMBPT Test memory with a 'barber-pole' test. RAMWBT Test memory with a 'walking bit' test. Screen Functions TNAME Return the address of the terminal name string. PSTRING Print a string to the console. INLINE Print a string following the call instruction. XYPSTRING Is the PSTRING routine with embedded cursor setup. XYINLINE Is the INLINE routine with embedded cursor setup. PCOUNT Print a string of (B) characters to the console. PMENU Print a menu to the console with cursor setups. PSTR Print a character a number of times. CLEAR Clear the screen. CLEOL Clear to end of line. CLEOP Clear to end of page. BELL Ring the console bell. CURSOR Set the screen cursor position. SETXY Use 2 memory bytes for cursor address setup. CRLF Send a carriage return line feed to the screen. ECHOLST Echo all screen output to the list device. LISTOUT Send all output to the list device. CONSOUT Re-select the screen as output device. USERIO Load addresses of custom I/O drivers. DISPATCH Send a character to output device(s). COE Send the accumulator to the screen. LOE Send the accumulator to the list device. LST Get the printers' output status. FLUSHLOE Flush line printer buffer. CIE Get a character from the console. CST Get the console status. CBUFF Read console into a console buffer. SETATT Set or clear console visual attributes. Numeric Printing LZB Leading zero blank for numeric output. BLZB Blank fill leading zero blank (right justify) for numeric output. CLZB Character fill leading zero blank (right justify) for numeric output. NOLZB Disable any LZB function. PHACC Print the accumulator as a HEX number. PDACC Print the accumulator as a DECIMAL number. PHDE Print DE as a HEX number. PDDE Print DE as a DECIMAL number. PSHDE Print DE as a signed HEX number. PSDDE Print DE as a signed DECIMAL number. Conversions ATOASC Convert HEX accumulator to ascii in HL. ATOHEX Convert ascii accumulator to HEX. ASCBCD Convert ascii string to a BCD number. ASCHEX Convert ascii string to a HEX number. HEXASC Convert HEX number to ascii string. HEXBCD Convert from HEX into BCD. BCDHEX Convert from BCD into HEX. NIBASC Convert low nibble of a to ascii in a. IHHL Input a HEX number into HL from the console. IDHL Input a DECIMAL number into HL from the console. COMP2S Convert DE into a 2's complement number into HL. CAPS Convert accumulator to capitals. CAPSBUF Convert a CP/M Console Buffer to capitals. Math Functions MULDH Multiply DE by HL. HL = result, overflow in DE. DIVDH Divide DE by HL. HL = result, remainder in DE. SQRT Take the square root of DE into HL. SGNADD Signed add HL to DE. SGNSUB Signed subtract HL from DE. COMDH Compare HL to DE. Zero if equal, Carry if DE > HL. Character String Functions CONSTR Concatenate two character strings. CMPSTR Compare two strings. POSSTR Find a substring in a string. CPYSTR Copy a substring from a string. DELSTR Delete a substring from a string. INSSTR Insert a substring into a string. CAPSTR Capitalize a string. PRNSTR Print a character string. BUFSTR Move a console buffer into a string. STRBUF Move a string into a console buffer. ELBSTR Eliminate leading blanks from a string. ETBSTR Eliminate trailing blanks from a string. DEFDEL Define token delimiters. GETTOK Get the first token from a string. TABSRC Search a table of strings for a string. Specialty functions and Utilities HLP Help file indexing and display manager. SETUP Terminal installation utility. FP Binary file display and patch utility. SMEPLI PL/1-80 assembly language specialty interface. TESTALL Hardware test program. TATT Terminal attribute test program. TASM Test of ASMLIB routines in general. RN Random number generator test program. 2. ASMLIB Commands in Detail ****************************** All the commands to the ASMLIB library are given below. The explanations in this manual assume a knowledge of both assembly language (intel style) and of Digital Research products. The use of RMAC, LINK and LIB are briefly described at the back of this manual. 2.1. Parameter Conventions ========================== In most cases register pair DE is the first operand and values are returned in register pair HL. If any command requires an 8 bit counter then it is passed in in register B and single byte values are passed in via register A. If more than 2 parameters are required then a table is pointed to by DE. 2.2. Miscellaneous Functions ============================ The miscellaneous sections is possibly the largest section of all since there are so many things that are useful that cannot be classified into a single cate gory. Table 2-1: List of abbreviations and Defaults Abbreviation Description ----------------------------------------------------------------------------- -> Pointer. Indicates that the register is pointing to a memory location. (XX) The contents of register XX. m(XX) Register XX points to a byte or world in memory. ms(XX) Register XX points to a string of bytes in memory. mb(XX) Register XX points to a console buffer in memory. Register Setup Convention A Register A always used for single byte data input. B Register B is used for single byte counters. DE Register DE is always used for input data values or for input memory pointers. HL Register HL is always used for the return of data or pointers to returned data and is sometimes used as a secondary data input register. ----------------------------------------------------------------------------- 2.2.1. PROLOG - Start a program ---------------------------------------------------------------------------- Entry parameters: None. Returned parameters: None. Registers affected: SP -> 64 level stack HL = Original SP BC = Return address of calling program. ---------------------------------------------------------------------------- This call sets up the stack for the users program and saves the old operating system or program stack address. This function should be called at the start of the users program. By using this function the user is able to later use the quit function to exit to the operating system without re-booting the CCP. This com mand is used as below and requires no parameters. A 64 level stack is initiali zed and data areas for other modules are also set up. The stack area is set up initialized with the bytes set to 'S' so that nesting depths can be checked by displaying the stack area. Most modules in ASMLIB will require prolog for their use since some data areas have been defined in prolog that are used by external modules for working storage. This has been done also to encourage its use. In dedicated applications (rom based) it is wise to use this function since a return from the program can be done to the calling program with the quit func tion which restores the users original stack pointer. start: call prolog ; Do the function ; ; Execution returns normally to here 2.2.2. QUIT - End a program ---------------------------------------------------------------------------- Entry parameters: None. Returned parameters: None. Registers affected: HL = Program SP ---------------------------------------------------------------------------- This function loads the stack address saved in the prolog call and uses it to return to the operating system directly. This of course assumes that the user has not corrupted the ccp (from whence we came). An example of this follows, note that it requires no parameters and that execution should now continue via CP/M or the address the program was called from. endprog: call quit ; 2.2.3. VERSION - Return Version number ---------------------------------------------------------------------------- Entry parameters: None. Returned parameters: H = Version number L = Release number Registers affected: HL ---------------------------------------------------------------------------- This function returns the current version of the library in the HL register pair. This function requires no parameters and an example follows. Register H returns the major version number and register L returns the revision number. getver: call version ; ; Register pair now contains the version number 2.2.4. CLKRD - Read real time clock ---------------------------------------------------------------------------- Entry parameters: DE -> 7 byte buffer to read time into Returned parameters: m(DE) contains time string Registers affected: DE ---------------------------------------------------------------------------- This function returns the time of day and date from the system clock. At present it supports the SBC-800 board with its onboard clock chip. It expects the user to point to a data area for the time and date to be read into using register DE. The time and date are stored in binary coded decimal format and are arranged in the following format. Clock Data Storage Allocation Byte Contents Range ------------------------------------ DE + 0 Year 0..99 1 Month 1..12 2 Day of month 1..31 3 Day of week 0..6 4 Hour 0..23 5 Minute 0..59 6 Second 0..59 An example of reading the clock follows. lxi d,clock$buff ; Point to the data buffer call clkrd ; Read the clock jmp continue ; Continue the program ; ; The following is filled in by the real time clock. ; clock$buff: db 0,0,0,0,0,0,0 ; 7 Bytes for date and time ; 2.2.5. CLKWR - Write to real time clock ---------------------------------------------------------------------------- Entry parameters: DE -> 7 byte buffer to write to clock Returned parameters: None Registers affected: DE ---------------------------------------------------------------------------- The clock write routine performs similarly to the clock read function except that the memory that is being pointed to is loaded into the real-time clocks' registers. This sets the clock. An example of this follows. start: lxi d,clock$buff ; Point to the source of time call clkwr ; Set the clock jmp continue ; Do next function ; ; The clock string below contains the time. ; clock$buff: db 83,08,14,20,10,20,00 The above string of bytes would set the clock to Tuesday the 14th of August 1983 at 10:20:00 am. Note that the seconds must be set to 00 and that usually 00 is the day of the week set aside for Sunday. The day of the week is set using the TOP nibble of the time/date string so that a Tuesday is represented with a 20h. 2.2.6. CHKRNG - Range check the accumulator ---------------------------------------------------------------------------- Entry parameters: A = Value to range check D = Value to check if A is => than E = Value to check if A is <= than Returned parameters: ZERO Flag if D <= A <= E CARRY Flag if A < E Registers affected: None, flags only ---------------------------------------------------------------------------- This entry checks if the accumulator value is between the values in the D and E register pair. This call will return a ZERO flag if the A register conforms to the following D >= A >= E. If register A is less than E then this call returns the carry flag set. If A > D then no carry or zero is set on return. An example of this call follows. lxi d,0503h ; D = 5, E = 3 lda value ; Read a previous saved value into A call chkrng ; Check if D <= A <= E jz inrange ; The equation is true jc atoobig ; A is > D jnc atoosmall ; A is < E The above example shows DE being loaded with the values to check A against. The accumulator is loaded from the location equated to 'value' which is assumed to have been previously loaded by another part of the program. The function call is performed by the call instruction. After the call the 3 conditional jumps take care of decoding where the accumulators value was in relation to the DE register pair. 2.2.7. CHKTBL - Table driven range checking ---------------------------------------------------------------------------- Entry parameters: A = Value to range check DE -> Table of values to check Returned parameters: Program Jump if test fails Registers affected: None, Flags only ---------------------------------------------------------------------------- This function call uses CHKRNG as its primitive but is expanded since it uses a table of values and addresses. The DE register is used to point to the table and its values are used to test the accumulator against. The addresses in the table are used if the accumulator fails to be within or equal to the values in the table. If the fails a test then a jump is executed to the users address. If the accumu lator passes the test then no jump is done and the function returns to the user with the DE register pointing to the next table element to be tested. By repea tedly using this function call, the user is able to do automatic range and error checking of accumulator values. An example of this call follows. start: lxi d,table ; Point to the table mvi b,3 ; Use this as a counter lda value ; Get a stored value loop: call chktbl ; Check against the table djnz loop ; Do 5 entries jmp continue ; table: db 100,50 dw fail1 db 90,60 dw fail2 db 80,70 dw fail3 In the above, the program sets up the pointer in the DE register and calls the function repeatedly. After each call the DE register pair points to the next table element. Note that the table stores the high value to check the accumula tor against first. The above 'dw' instructions store the address of the failure routines to be executed when the accumulator does not fit between the high and low values. When the jump is performed the top of the stack does not point to the return to the table checking routine. It is important for this reason for the users soft ware to exit from the failure routines in an orderly manner and not to expect to return to the table checking loop. When the failure address in executed the accumulator and DE registers are intact. 2.2.8. FORMIN - Formatted display & input ---------------------------------------------------------------------------- Entry parameters: DE -> Table of strings and addresses Returned parameters: Console buffers filled in Registers affected: A ---------------------------------------------------------------------------- This routine allows the assembly language programmer to create screens of infor mation which accept console input in a formatted manner. The screens of informa tion are displayed on the console and the user is prompted for data to be keyed into the provided fields. This function is called with DE pointing to a section of code which contains the prompting strings and pointers to standard console buffers which are used for reading the console into. The format of this is shown below. menu: ; This is the text section db 1,13,'Enter Surname $' dw name$buff ; Point to a console buffer db 3,5,'Enter First name $' dw sur$buff db 5,17,'Enter Age $' dw age$buff db 0ffh ; Indicate the end of the menu ; name$buff: db 25,3,'Mr ',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 sur$buff: db 25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 age$buff: db 3,0,0,0,0 In the above code the menu section contains strings which are preceded by screen X and Y addresses for the text to be printed. After the addresses the text is printed till a '$' is found. The next line contains the address of the console buffer to be associated with this string. If the address of the text buffer is 0000 then formin assumes that NO console characters are to be read and merely prints this menu string. This is useful for placing text on the screen without the necessity for inputting characters into a console buffer. The user is allowed to load default text into the buffer. In the above case the 'Mr ' was loaded. When the screen is displayed then this would be displayed as 'Mr _______________________' . The user is able to backspace over the 'Mr ' if needed. To call the routine requires the following code. lxi d,menu ; Point to the menu call formin ; Do it The action in this example would be to print the 3 menu strings on the screen then to position the cursor just after the 'Mr '. The user at this point is able to... a) Enter text to fill in the line. Backspaces or delete keys cause characters to be erased and underlines to be returned. A control X causes the whole line to be erased. At the end(s) of the line the program will ring the console bell if the user tries to continue input. b) Enter a carriage return which will cause the cursor to be positioned onto the next menu text item. Any text that is in there already will be to the left hand side of the cursor. c) Enter an up arrow or control K which will cause the cursor to be positioned after the previous menu item. d) Enter an escape which will terminate the input on the screen and return to the users program. Note that this routine does not do any checking of data nor input except that all control codes except those mentioned are ignored (ring the bell). In order to allow the user to display text on the screen without having to enter text for it, any console buffers located at 0000h cause formin to print these message strings only. This is handy for putting prompts after a prompted input, as mentioned above. 2.2.9. MON - Stand-alone Monitor ---------------------------------------------------------------------------- Entry parameters: None. Returned parameters: None. Registers affected: A ---------------------------------------------------------------------------- This function call causes a complete monitor to be loaded into the resultant program. The code overhead for this is quite large since the monitor is exten sive. This monitor is the same as is used on SME Systems SBC-800 and has exactly the same commands except that some of the unsuitable commands have been removed. The complete description of the commands is available from the SME Systems moni tor manual and is available at small cost. The function call to the monitor is as follows. Note that this routine loads about 1.5k of code. call mon ; Call it. Simple When the monitor exits the users program will be re-entered using a return inst ruction. 2.2.10. ATODIN - Analogue to digital input ---------------------------------------------------------------------------- Entry parameters: DE = Analog channel to read Returned parameters: HL = Analog value or 0FFFFh if error Registers affected: A, HL ---------------------------------------------------------------------------- This function returns the data from an analogue to digital converter. This func tion uses DE to contain the channel number of the converter to be read and re turns the value in the HL register pair. If the converter is non-functional then the Hl register will contain 0ffffh which indicates an error. An example of this follows. lxi d,0007h ; Read channel number 7 call atodin ; Read it mov a,h ; get top data value cpi 0ffh ; error ? jz error ; YES if a = ff ; ; Here the HL register contains the analogue to digital value. ATODIN is a highly machine dependent piece of code and in the current version of ASMLIB uses the ADC-32 card from SME Systems. This card is port addressed and must be set to reside at 040h. Since this card has 32 channels of analogue in put, any channel request greater than 32 returns an error which is the 0ffffh in register HL. 2.2.11. PORTSET - Port initialization ---------------------------------------------------------------------------- Entry parameters: DE = Table of bytes and values Returned parameters: None. Registers affected: A---------------------------------------------------------------------------- This function has been written to allow strings of bytes to be sent to output ports very easily and quickly so that many ports can be sent data strings of different bytes. This is table driven and expects the table to be in a set for mat. An example table follows. table: db 8,08bh,004h,0c4h,001h,000h,003h,0c1h,005h,0eah db 8,08ch,004h,044h,001h,000h,003h,0c1h,005h,0eah db 0 In the above example the first byte indicates the number of bytes to be sent to the I/O port whose address follows. If the byte is 00 then no bytes are to be sent so the function returns to the user. In this example the 8 bytes 004 to 0eah inclusive are sent to port 08bh. Next the function reads the 08 on the next line and does the same thing with these bytes except that it sends them to port 08ch. Lastly, the 0 byte causes a return to the user. start: lxi d,table call portset ; 2.2.12. DELAY - Perform accurate time delay ---------------------------------------------------------------------------- Entry parameters: DE = Number of milliseconds Returned parameters: None. Registers affected: A ---------------------------------------------------------------------------- This function uses DE as a counter to produce an accurate millisecond delay for 4Mhz processors. The routine has a 40 clock cycle overhead so that DE = 1 produ ces 1ms + 40 clock cycles and 0ffffh = 0ffffh ms + 40 clock cycles delay. This is used as follows. start: lxi d,1000 ; 1 second delay call delay ; 2.3. Cyclic Redundancy Codes ============================ A cyclic redundancy code or CRC (sometimes called a checksum) is the result of a mathematical manipulation of data that produces a result that is as close to unique for the data input as possible. The whole basis of use for a CRC is that if the data changes in any possible way, then the CRC MUST also change. Since the CRC result from the math manipulation is only 16 bits long, the code can never be unique for all strings of data. Results of 16 bits have proven to be most effective though since in practice the chance of two different pieces of code having the same CRC is small. The real use of the CRC is to detect if two copies of the SAME code are different or not. It is guaranteed that if the CRCs' do not match then the software is corrupted somewhere. CRCs' are useful for making sure that pieces of data have not been corrupted since the user is certain in the knowledge that if data does not match its chec ksum then the data has been changed. The CP/M Users group disks contain a list of the CRC's of the files on their disks so that end users can check if any files have been corrupted. Also provided through the User Groups is a program that reads disk files and produces CRCs' for the data (SME Systems has a better one !) so that different copies of the same program can be compared. For the above reason of data checking, the distribution disk has a list of CRC's on it which may be quickly checked with the command A>CRCK CRCKLIST C This command causes the list of CRC's to be read and then all the individual files are read and checked against the list. Any files that fail cause the con sole bell to ring and an error message is printed. This is an aid to knowing that the disk you receive is a good copy of the original. Practical uses for CRCs' can include checking if serial data transmission over modem type links has been correct (ie check that the CRCs' match the data at the receiver). Another similar use for CRCs' is checking that the operating system of your computer is working correctly and that memory controller etc are all ok. The software presented in ASMLIB is intended for use in CRC generation programs. These routines have been pulled from the Users Groups and are used in CRCK.COM which is the CRC generator program from SME Systems and the Users Groups. The base idea behind these routines is that the user calls the clear routine which clears the current CRC then repeatedly calling the ADDCRC routine so that the data is 'added' to the current CRC. The GETCRC routine can be called when all data has been sent to the ADDCRC routine to extract the resultant 16 bit CRC code. 2.3.1. CLRCRC - Clear Current CRC --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This routine clears the current accumulated CRC that is stored in data memory. This routine is usually called before any data is sent to the main CRC routines so that the main routines start with cleared/initialized memory. It is also called when one CRC has been extracted from a piece of data and a new piece of data is to have a CRC generated from it. This is an initialization routine. 2.3.2. ADDCRC - Adding a byte to the current CRC --------------------------------------------------------------------------- Entry Parameters: A = Byte to add Returned Parameters: None Registers Affected: None --------------------------------------------------------------------------- This function passes a byte into the mathematical formula that generates the CRC's. The byte is operated upon according to the polynomial X^16 + X^15 + X^13 + X^7 + X^4 + X^2 + X + 1 In normal use, the programmer sends blocks of data (usually 128 bytes or whatever) to ADDCRC on a byte by byte basis (note that CLRCRC is called at the start of the block) and at the end of the block of data, the CRC is returned using the GETCRC routine. 2.3.3. GETCRC - Return the current CRC --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: HL = Current CRC Registers Affected: HL --------------------------------------------------------------------------- This function returns the current 16 bit CRC value in the HL register pair. The example which follows performs a CRC on the section of memory between 80 and 0ffh (default sector buffer) and prints the result to the screen. start: call clrcrc ; Clear any existing crc lxi h,080h ; Point to start of data area mvi b,080h ; Number of bytes to do crc$loop: mov a,m ; Fetch the byte to 'CRC inx h ; Point to next byte for later call addcrc ; Add it to the running CRC djnz crc$loop ; Do all 80h bytes ; call inline db 0dh,0ah,'CRC of 80..FFh = $' call getcrc ; Return the value in HL xchg ; Load into DE for printing call phde ; Print as a hex number ret ; ; End 2.4. Random Number Routines =========================== The four random number routines were written after a good looking random number source stub was found in the user groups. After testing of the modules written around the stub it was decided that its inclusion into the library was warranted since the results of the tests indicate that the generator is as good as any on the market anywhere. One improvement on the original source code was the addi tion of an initialization entry point which uses the Z-80 refresh register to pre-jumble the seed fed to the generator. The random number generator had to be tested before being considered for inclu sion into ASMLIB, and these tests are described below and their source code is included on the distribution disk and called RANDTEST.ASM / .COM and may be run immediately. The first test of the randomness of the generator is performed by using the 8 bit output mode to update a 256 element table a large number of times so that as each number arises, the table element is incremented. After running this for a long time (6 million iterations) the results are displayed on the screen and the expected output is compared to the theoretical output. In all cases the devia tion from the expected is so small that complete confidence in the generator can be assured. The second test of the generator uses the 16 bit output to also update the 256 element array by dividing the random number output by 256. This test produces excellent results also after 6 million iterations. In both tests the program displays the expected result for the number of times that an number may occur in the table. The expected number is obtained by dividing the number of iterations by the table size (or number range). 2.4.1. RANDINIT - Initialize random number seed --------------------------------------------------------------------------- Entry Parameters: DE -> Random number byte seed string Returned Parameters: ms(DE) Jumbled with refresh register Registers Affected: None --------------------------------------------------------------------------- This function uses the Z-80 refresh register to jumble the seed that is fed into the random number generator. This jumbling is quite effective since running a program with the same seed a number of times produces different random number outputs. The seed that is fed to this and other random number routines must be at least 5 bytes long. This function may also be used for scrambling standard character strings (of length > 5) though no possible use can be thought of for this and there would be a lot of work required to 'de-scramble' a string jumbled like this. On entry, this routine expected DE to point to the start of the seed string. The length of the seed string is stored in the first byte of the seed string. 2.4.2. RAND8 - Return an 8 bit random number --------------------------------------------------------------------------- Entry Parameters: DE -> Random number byte seed string Returned Parameters: A = random number Registers Affected: A --------------------------------------------------------------------------- This function expects DE to point to the seed and will return the 8 bit random number in register A. 2.4.3. RAND16 - Return a 16 bit random number --------------------------------------------------------------------------- Entry Parameters: DE -> Random number byte seed string Returned Parameters: HL = random number Registers Affected: HL --------------------------------------------------------------------------- This function is identical to RAND8 except that the random number returned is 16 bits long and returned in register HL. This function is performed by inter nally calling the RAND8 function twice and placing the results in the H then L registers. 2.4.4. RANDP16 - Return positive 16 bit number --------------------------------------------------------------------------- Entry Parameters: DE -> Random number byte seed string Returned Parameters: HL = random number Registers Affected: HL --------------------------------------------------------------------------- This function returns a positive 16 bit number by clearing the most significant bit in the 16 bit result. This has been done to limit the range of the number so that it is always positive. The following example shows how to use all the ran dom number routines. start: lxi d,seed ; point to the seed call randinit ; randomize it with refresh call inline db 0dh,0ah,' 8 bit random number = $' call rand8 ; NOTE: DE still --> seed call pacc ; display it ; 16 bit version now call inline db 0dh,0ah,'16 bit random number = $' call rand16 xchg ; load into DE call pdde ; display as a decimal ; 16 bit positive version. xchg ; now DE -> seed again call inline db 0dh,0ah,'16 bit positive number = $' call randp16 xchg ; load from hl -> de call pdde ; all done........ 2.5. Memory test routines ========================= The memory test routines have been provided mainly for the code hackers who want to play with testing their memory and partly for the dedicated (rom based) users so that memory in dedicated machines can be tested before program operation. This feature when installed in dedicated machines is very impressive on paper (in the specs) and is found in the more expensive and well engineered devices. 2.5.1. RAMBPT - Barber pole memory test. --------------------------------------------------------------------------- Entry Parameters: DE -> First byte of memory to be tested HL -> Last byte of memory to be tested Returned Parameters: HL = Error address A = What the byte in memory should be Registers Affected: CARRY and an error has occurred A, BC, DE, HL all changed --------------------------------------------------------------------------- This function uses a 'barber pole' type of test pattern to write to memory. This type of test pattern uses an odd number of bytes in its test so that is will catch address boundary errors and stuck bits. The source code for this module is provided since this test should be optimized for the memories in use which may be either 1, 4 or 8 bits wide. Far more complete documentation of the theory of the bit patterns and why they are used for the different memory widths is in the RAMBPT.ASM source code module. As standard, the module is set up for 1 bit wide memories though this is changed with the size equate then re-assembling and re- linking the module into the library. The end of this manual describes changing modules within ASMLIB. On entry to this routine the DE register is expected to point to the start of memory to be tested and HL to the last address to be tested. If an error is found then the program will exit with carry set and HL will point to the byte in error and register A will contain what should be in the memory address. If memo ry passes the tests then carry will not be returned set. These routines do not preserve any registers so do not hold any temporary values in them. This is one of the few routines in the whole of the library that cor rupts registers. BEWARE. start: lxi d,ramstart lxi h,ramend call rambpt ; test from start -> end jrnc ram$is$ok ; ram is ok then ; if here then ram has an error at hl. call inline db 0dh,0ah,'Memory error...... etc ; 2.5.2. RAMWBT - Walking memory test --------------------------------------------------------------------------- Entry Parameters: DE -> First byte of memory to be tested HL -> Last byte of memory to be tested Returned Parameters: HL = Error address A = What the byte in memory should be Registers Affected: CARRY and an error has occurred A, HL, DE, BC all changed --------------------------------------------------------------------------- This test checks memory by filling it with a byte then checking if the byte can be read back. If an error is encountered then the carry bit is returned set and the memory address where the error happened is returned in HL and the byte that was written to memory is returned in register A. The bytes that are sent to memory by this test are 0, 0AAh, 055h, 080h,040h,020h,010h,08,04,02,01 respectively. After one of the above bytes is sent to the whole of the memory under test it is tested if it can be read back. On entry DE points to the start of memory to be tested and HL points to the last byte to be tested. This test is used (therefore) exactly the same way as the RAMBPT test described above. 2.6. Screen Functions ===================== The screen functions are responsible for all console I/O and handle simple cha racter / text output. The I/O drivers are supplied standard for the CP/M opera ting system and run under version 2.2 or 3. These represent a very wide cross section of all the source code files surveyed and represent the best featured found and many features that were left out of programs because they were too hard. The routines have been laid out so that register setups are very simple and similar in concept to all other routines in the library. In the following routines, no initializations of any devices are done, the soft ware assumes that the I/O devices are ready for immediate I/O. On the distribution disk are a number of source code modules for CP/M and other hardware. These files are described in the I/O Routines and Modification sec tion. Many of the routines described in this section are very powerful, elegant and pleasing to use. Since there is such a wide of terminals available on the market all with different codes and attributes, some compromises have been made. To aid in converting the library to a terminal, all the hardware dependent routines have their source code files included on the distribution disk. Also provided is an application program that will patch the hardware dependent routines of a program to suit a particular program. This is described in detail later also. NOTE: Most of the string routines assume that the string to be printed is termi nated by a '$' and does NOT have a leading character count. 2.6.1. TNAME - Return Terminal Name String Address --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: HL -> 6 Byte terminal name Registers Affected: HL --------------------------------------------------------------------------- The SETATT and SCREEN modules have some highly hardware dependent code that must be tailored to suit the characteristics of the terminal in use. Tailoring is described later. Since there are many different terminals available, it was decided to have a facility whereby it is possible to interrogate ASMLIB to find out which terminal a program has been coded for. In the hardware dependent modules there is a string of 6 ascii text bytes which this function returns the address of. This ascii string is completely dependent on the user when patched with SETUP program so that the name of the terminal that the module suits can be saved. An example which displays the name of the terminal that has been installed is in the TESTALL and TATT programs and also shown below. This gets the address of the six byte string then prints it after a little explanatory message. call inline db 0dh,0ah,'Terminal codes to suit a $' call tname ; HL = terminal ID string address xchng ; DE -> terminal name mvi b,6 ; print only 6 bytes call pcount ; print the string to the string The standard library supports an ABM-85 terminal which is very similar to TVI- 910, TVI912, ADM-31, ADM-3A type terminals. This can be changed for individual programs using the SETUP program. 2.6.?. IONUM - Return I/O driver number --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: L = I/O driver number Registers Affected: L --------------------------------------------------------------------------- This function returns the NUMBER of the I/O driver module that has been instal led in ASMLIB. This allows an application program to test if an application program has been linked to the correct I/O driver module. At present, there are four I/O driver modules which return the following code numbers in the HL register pair. Since the source code for all the I/O driver modules has been included on the distribution disk, the user has complete free dom to change or generate unique I/O driver numbers to suit himself. This func tion has been implemented so that an application could test for a correct libra ry module, use it in this spirit. An example of use is in the TESTALL program which checks this number before any test of MPC-6 card is attempted so that a check is made for codes 3 and 4. This was necessary with this program as it requires MPC-6 interface software from the I/O driver modules that support it. Table 2-3: IONUM Function return codes --------------------------------------------------------------------------- Number Driver name --------------------------------------------------------------------------- 00 Illegal value, indicate a link error 01 CP/M standard I/O drivers 02 SBC-800 I/O drivers 03 CP/M and MPC-6 I/O drivers 04 SBC-800 and MPC-6 I/O drivers --------------------------------------------------------------------------- An example of using IONUM is as shown below. call ionum ; no setups required mov a,l ; check if 00 ora a jz link$error ; terminal linkage error ; ; Register A and L will now contain the I/O driver number as shown in the table 2.6.2. PSTRING - Print a string of text --------------------------------------------------------------------------- Entry Parameters: DE -> String of characters Returned Parameters: None Registers Affected: DE, A --------------------------------------------------------------------------- This routine prints the string which is pointed to by DE to the screen till a '$' is read in the string. This is a very simple routine and is found in many source code files. string: db 'This string is sent to the screen$' ; start: lxi d,string call pstring ; 2.6.3. INLINE - Inline text string printing --------------------------------------------------------------------------- Entry Parameters: Return address has a string Returned Parameters: None Registers Affected: DE, A --------------------------------------------------------------------------- This routine causes the text which is pointed to by the return address to be printed on the screen. Since the return address contains the address of the string, it is vital never to jump to this routine. Code immediately after the string is executed after the string has been printed. start: call inline ; Must use a CALL instruction db 'This text is printed too$' ; Execution returns here. 2.6.4. XYPSTRING - Cursor address string printing --------------------------------------------------------------------------- Entry Parameters: DE -> string and cursor addressing bytes Returned Parameters: None. Registers Affected: DE, A --------------------------------------------------------------------------- This routine is a modified form of the PSTRING routine which expects the text string to be preceded by an X and Y screen address to be used to cursor position the text to. The following example prints the message in the middle of the screen. string: db 23,12,'Print this in the middle$' ; start: lxi d,string call xypstring ; 2.6.5. XYINLINE - Cursor addressed inline printing --------------------------------------------------------------------------- Entry Parameters: Return address has a string and XY bytes Returned Parameters: None. Registers Affected: DE, A --------------------------------------------------------------------------- This is a modified form of the inline routine which expects the text string which is pointed to by the return address to be preceded by a cursor address. The following example would do the same as the above example for xypstring. call xyinline db 23,11,'Print this in the middle$' ; Execution returns here. 2.6.6. PCOUNT - Counted character string printing --------------------------------------------------------------------------- Entry Parameters: DE -> Text menu or screen Returned Parameters: None. Registers Affected: DE, A --------------------------------------------------------------------------- This function prints the string which is pointed to by DE and is assumed to (B) characters long. This is useful if a text string is known to contain '$' charac ters which must be printed or if a string without a terminating '$' character is to be printed. The following example illustrates this. string: db 'I Have got $ symbols in my eyes' ; start: mvi b,31 ; Print the whole string. lxi d,string ; Point to it call pcount ; Print it ; All done. 2.6.7. PMENU - Menu display routine --------------------------------------------------------------------------- Entry Parameters: DE -> Text menu or screen Returned Parameters: None. Registers Affected: DE, A --------------------------------------------------------------------------- This function will print a slab of text that has embedded cursor control screen addresses. This is particularly useful for printing menus of actions/functions that an operator will view to make a choice. The example that follows shows a menu that is composed of X and Y screen addresses followed by strings of ascii text terminated with '$' characters. The menu is terminated with a 0ffh byte which causes execution to return to the user. menu: db 01,3,'1) General Ledger$' db 01,5,'2) Stock Update$' db 01,7,'3) Payroll$ db 40,3,'4) Accounts Payable$' db 40,5,'5) Accounts Receivable$' db 0ffh ; start: lxi d,menu call pmenu ; Print the above menu ; 2.6.8. PSTR - Repeated character printing --------------------------------------------------------------------------- Entry Parameters: A = Ascii byte B = Counter Returned Parameters: None. Registers Affected: B, A --------------------------------------------------------------------------- This function prints the character in the accumulator (B) times to the screen. This is useful for underlining pieces of text or for filling up regions of the screen with characters. mvi b,20 ; do 20 times mvi a,'-' ; Dashes call pstr ; Print 20 dashes ; 2.6.9. CLEAR - Clear the console screen --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This causes the screen to be cleared. This is a very hardware dependent function but is easily patched using the SETUP program or by patching the supplied source code file for this function. The source code file SCREEN.ASM may be modified and linked to change this (and other dependent) function. call clear ; Clear the screen ; 2.6.10. CLEOL - Clear to end of line --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function causes all the text on the current line from the present cursor position to the right hand edge of the screen to be erased. The cursor position is unchanged by this function. This function is useful for clearing sections of screen images and for printing special menu or data images. This function re quires only a call. call cleol ; 2.6.11. CLEOP - Clear to end of page --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function clears all the text from the current cursor position to the bottom most corner of the screen to be erased. The cursor position is unchanged by this function. This function is used to selectively clear the screen and is much faster and elegant than using program code and cursor positioning to do the selective clears. This function only requires the following call call cleop ; 2.6.12. BELL - Ring console bell --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This routine rings the console bell. This is a trivial routine but is found in many programs and is quite useful. It expects no parameters. An example of this call is start: call bell ; 2.6.13. CURON - Enable the cursor --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function causes the screen cursor to be re-displayed or enabled. This func tion is used after the cursor has been turned off for some reason and is now required to be displayed again. It is used with only a simple call call curon ; 2.6.14. CUROFF - Disable cursor --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function turns the cursor off or disables it. This is particularly useful when a screen has to be filled with text or data and a flashing cursor is not required. It is used with only a simple call as follows. call curoff ; 2.6.15. CURSOR - Position the cursor --------------------------------------------------------------------------- Entry Parameters: D = X screen address E = Y screen address Returned Parameters: None. Registers Affected: DE --------------------------------------------------------------------------- This function uses the values in DE to set the screen address of the cursor. Register D must contain the X address and E contains the Y address. This func tion is also hardware dependent and should be changed to suit the users termi nal. At present the software sends an Escape (01bh) then an '=' then the Y screen address+32 then the X screen address+32. The source code for this func tion is contained in the SCREEN.ASM assembly language module. The example fol lowing shows this routine being used to position the cursor to screen address 10,11 (X and Y resp). start: lxi d,0a0bh ; Line 10, column 10 call cursor ; set up ; 2.6.16. SETXY - Indirect cursor position--------------------------------------------------------------------------- Entry Parameters: m(DE) = X screen address m(DE+1) = Y screen address Returned Parameters: None. Registers Affected: A, DE --------------------------------------------------------------------------- This function uses the address in DE which points to 2 bytes which are assumed to be the X and Y addresses for screen cursor positioning. This is shown below. x: db 0ah ; X address = 10 y: db 0ah ; Y address = 10 ; start: lxi d,x ; Point to the address call setxy ; set up ; 2.6.17. CRLF - Print a cr, lf pair --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This causes a carriage return line feed pair to be sent to the console. This sounds too simple to be true, but in most programs this is indeed a standard piece of code. It has been included in the library since it is so popular and so easily and often used. The example for this is... call crlf ; 2.6.18. ECHOLST - Echo console to printer --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This function causes all characters which go to the screen to also go to the current CP/M list device. This is similar to the control-P CP/M function which operates through the CCP. Echolst uses a data byte to process this function and is quite fast in operation. One important thing with this routine is that any cursor positioning that is implemented will not be effective on the printer unless it is completely compatible with the screen addressing in use. This call expects no parameters and an example follows. start: call echolst ; Echo character to list device ; 2.6.19. LISTOUT - Re-direct console to printer --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This function DISABLES all output that would have gone to the screen and causes it to be sent to the list device. This also uses a data byte code to direct characters. An example of this follows. start: call listout ; 2.6.20. CONSOUT - Restore console output (default) --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This function disables any list output echo or listout function and causes all output that was meant for the console to actually go to the console. This is the default condition of the library. This call will normally only be used when the console or printer have been re-assigned by the user and need to be reset. This function is called as follows and expects no parameters. start: call consout ; 2.6.21. USERIO - Patch user console I/O drivers --------------------------------------------------------------------------- Entry Parameters: DE -> I/O driver jump table Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This routine allows the user to patch the addresses of character input and out put I/O drivers into the ASMLIB software. This allows the user to insert custom drivers which cause all console I/O to go through selected drivers.This is handy for testing hardware devices quickly. Another possible use for this is to allow the user to quickly select one of many possible output devices which are selec ted according to program control.This routine uses the address passed in DE to point to 3 addresses which are the console input and console output and console status routines respectively. These addresses are loaded into internal storage and are used for all further console I/O. An example follows which causes the first serial channel on an SME Systems SPC- 29 to become the standard console. Note that all characters sent to DISPATCH (and COE) will now go to this device until one of the other re-direction func tions are called which disables this function. start: lxi d,drivers call userio jmp continue ; continue on elsewhere ; drivers: ; Custom I/O driver table dw userin ; Console input dw userout ; Console output dw userst ; Console status ; userin: ; Read a character into accumulator call userst ; get the status jrz userin ; wait in 010h ; Read data port ani 07fh ; Mask off top bit ret ; userout: ; Send the accumulator to users device push psw ; Save the character userout1: in 011h ; Get serial status ani 01 ; output ready (empty) jrz userout1 ; Wait for transmitter pop psw out 010h ; Send to output ret ; userst: ; Users status routine in 011h ; Read SPC-29 status ani 02 rz ; Not ready mvi a,0ffh ; Ready flag In the above I/O drivers, they return direct to the users software and do not have any handshaking. This means that any registers corrupted by the users I/O drivers stay corrupted and also that any characters read are returned to the user as is. Masking parity bits off may be necessary for console input routines. The console output routines should expect output ascii characters in the accumu lator, as shown above. This routine will be very useful for rom based applica tions and is discussed later for just this application. 2.6.22. DISPATCH - Print a character --------------------------------------------------------------------------- Entry Parameters: A = Ascii character Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function sends the accumulator ascii character to the currently selected output device(s). This function should be used to send characters to devices selected with the listout / echolst / consout functions. This routine also has X-on / X-off handshaking implemented which synchronized it to slow terminals. An example follows. Characters sent to this output routine will also be vectored off to the userio routines if these are selected. start: mvi a,'-' ; Get an ascii character call dispatch ; 2.6.23. COE - Directly print a character --------------------------------------------------------------------------- Entry Parameters: A = Ascii character Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function sends the accumulator to the standard CP/M console. This function bypasses the re-direction and handshaking that is available with DISPATCH through the consout / listout / echolst functions. The following is an example of this function. (USERIO may over-ride this function) start: mvi a,'*' ; Load character call coe ; Send to screen ; 2.6.24. LOE - Send a character to the printer --------------------------------------------------------------------------- Entry Parameters: A = Ascii character Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function sends the accumulator ascii character to the list device. This function has similar consequences to the coe function. The following is an exam ple. start: mvi a,'+' call loe ; 2.6.25. LST - Return printer output status --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: A Returns list device status Registers Affected: A --------------------------------------------------------------------------- This function returns the status of the print device. This status indicates if the printer can accept another byte for output. As with the CST function, this function returns a zero flag if it cannot accept another character. An example of this function is shown below which uses LST to wait for the printer so it can keep sending bytes to it. start: call lst jz start ; wait for not busy mov a,m ; get a character ora a ; end ?? jz quit call loe jmp start If the print device does not have a status return facility then this function will always return a ready (non zero) flag so that it is up to the printer dri ver to wait for a non-busy status. 2.6.26. FLUSHLOE - Flush line printer buffer--------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function was written so that the buffer of a line printer (centronic paral lel type) can be flushed to the paper before it would normally otherwise be. The need for this arises when the printer does not print its buffer till it receives a paper movement command. But what if you do not want to move the paper, or want to display the results immediately? The answer to this is to flush the unprinted portion of the line buffer to the paper. Sadly though there does not seem to be any means of doing this with most printers, but a simple trick may well work for printers other than the authors own. This trick is to send a backspace then a space to the printer. In most printers this causes the printer to flush its buffer then to backspace over the character. The space causes the print head to return to where it should be. This works well with epson type printers such as the PX-80 and a few others. This routine requires no parameters and is used as shown. start: call flushloe ; all done 2.6.27. CIE - Read a console character --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: A returns a console character Registers Affected: A --------------------------------------------------------------------------- This function waits for a character to be typed at the system console. The cha racter is returned in the accumulator as is shown below. Note that userio rou tines may be invoked with this function if these have been selected. start: call cie cpi 0dh ; was it a carriage return ? ; etc etc etc. 2.6.28. CST - Return the console status --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: A returns console character Registers Affected: A --------------------------------------------------------------------------- This function returns the status of the system keyboard input device. If the accumulator returns a zero value and flag then no character is ready at the keyboard else the value of 0ffh indicates a character is ready. Note once again that userio routines may be invoked with this call. An example follows. start: call cst jnz get$char ; Get the character. ; No character ready if here 2.6.29. CBUFF - Read the console into a buffer --------------------------------------------------------------------------- Entry Parameters: DE -> Console buffer Returned Parameters: mb(DE) contains console characters Registers Affected: A, DE --------------------------------------------------------------------------- This function reads the console into a standard CP/M console buffer pointed to by DE. This function does not use the operating system since if it is required for software to run in rom, no tailoring will be required. Some other routines (FORMIN, IHHL, IDHL etc) call this routine. One modification of this routine to others of its type is that it allows the operator to have a partially initialized data area. When this is done, the data area is displayed and the cursor is positioned after the data so that default conditions can be set up. An initialized data area is recognized by the string length (second byte in the buffer) being non zero. The only problem with this is that the user must clear the length byte in the console buffer if no data is to be displayed. This routine accepts delete/backspace, linefeed, carriage return and control X as valid input characters and processes them exactly the same as CP/M does. Any other control characters are displayed with a preceding '^' symbol with the control character being placed in the data string as is. An example of how to set up the routine follows which shows the data area pre- initialized with the 'MR.' bytes. This will be displayed on the screen as 'MR.' with the cursor positioned after the full stop. ; ; Declare an 8 character string to read into. ; string: db 8,3,'MR. ' ; start: lxi d,string call cbuff ; read console into string. ; 2.6.30. SETATT - Set console visual attributes --------------------------------------------------------------------------- Entry Parameters: A attribute to be set Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function causes visual attributes to be set or cleared on the system con sole. The accumulator must contain a value that is in the legal range of expec ted values. The current version of software supports the Kimtron ABM-85 termi nals and is very easily changed by editing and re-assembling the source code file or using the SETUP program to patch in a terminal definition. It is possible that the current software will suit other terminals since the Kimtron terminals look to have a 'standard' set of visual attributes. This type of terminal is very close to the Televideo series and the ADM-3a. When this function is called, the code that the accumulator selects is sent to the console and all subsequent characters sent will be displayed according to the selected attribute. When an attribute is selected, the previous attribute that was enabled is auto matically cleared. When the user issues a 00 in the accumulator, the function clears the current attribute and does not select a new one so that text will be displayed normally (back into default mode). At present the accumulator may take on the following codes. Screen Attribute Codes Value --- Action --- 00 Clear any current attribute. 01 Select half intensity. 02 Select blinking characters. 03 Select reverse video characters. 04 Select underlined characters. All the above functions are very hardware dependent and it is expected that the user will need to vary the codes that are sent to the console when a different console is used. The file 'SETATT.ASM' contains both the current default codes and the program for indexing into the table. Please refer to this file if modi fications are necessary since this file contains much text on how to do any modifications necessary. SETATT is called as follows. start: mvi a,1 ; Select 1/2 intensity call setatt ; do it call inline db 'This is printed in 1/2 intensity$' xra a ; put a 00 in accumulator call setatt ; Clear the attribute ; Continue on. 2.7. Numeric Printing Functions =============================== The printing of both hexadecimal and decimal numbers has always been a most painful piece of assembler programming and so as to make this easier, the follo wing routines are supplied. These routines allow the user to print 16 bit signed or unsigned numbers as either decimal or hexadecimal (nice) and also performs 4 types of leading zero blanking (lovely) on the output. These functions will possibly be some of the most used as they are common in many programs that send any numeric output to the screen. 2.7.1. LZB - Select leading zero blanking --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function causes any numeric output to be leading zero blanked. LZB requires no parameters and an example is shown below. start: call lzb ; Enable leading zero blanking ; 2.7.2. BLZB - Right justify LZB --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This enables blank filled leading zero blanked so that any leading zeros in a number are replaced with blanks before being printed. This is useful for making tables of numbers right justified. An example of this function in action is the screen and printer outputs of the random number generator test program (RN) that has neatly ordered tables of numbers. This function also expects no parameters and an example follows. start: call blzb ; 2.7.3. CLZB - Character fill LZB --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function uses the character in the accumulator for character fill leading zero blanking of numeric printing routines. This type of output is useful in some situations such as cheque printing where leading zeros are replaced with '*' characters. An example follows. start: mvi a,'*' ; Use this for filling call clzb ; Leading zero char fill ; 2.7.4. NOLZB - Disable LZB --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This disables all forms of leading zero suppression. An example follows. This is the default type of leading zero printing mode. start: call nolzb ; 2.7.5. Sample LZB function Output --------------------------------------------------------------------------- The following table shows the expected output from the above types of leading zero suppression. All numbers here are assumed to be decimal (though the same holds for hex). This type of output is also to be expected from single register (accumulator) value printing. Table 2-5: Leading Zero Sample Output Number nolzb lzb blzb clzb (using *) ---------------------------------------------------- 0000 00000 0 0 ***0 0001 00001 1 1 ***1 0010 00010 10 10 **10 0100 00010 100 100 *100 1000 01000 1000 1000 1000 ---------------------------------------------------- 2.7.6. PHACC - Print the accumulator as hex digits --------------------------------------------------------------------------- Entry Parameters: A = hex digit 0 .... FF Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function prints the value in the accumulator as a hexadecimal number. Lead ing zero suppression, as with all numeric printing, is done according to the last use of a leading zero formatting function call. An example of this function follows. start: lda value ; load accumulator call phacc ; print in hex base ; 2.7.7. PDACC - Print accumulator as decimal digits --------------------------------------------------------------------------- Entry Parameters: A = value 0 ... FF Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function converts the accumulator to decimal and prints it to the screen. This function is used exactly the same as PHACC. 2.7.8. PHDE - Print DE as hex digits --------------------------------------------------------------------------- Entry Parameters: DE = value 0 ... FFFF Returned Parameters: None. Registers Affected: None.--------------------------------------------------------------------------- This function print the DE register pair as a hexadecimal number. Depending on the leading zero mode, up to 4 digits may be printed depending on the value. An example follows. start: lded value ; Load a 16 bit value in call phde ; Print it ; 2.7.9. PDDE - Print DE as decimal digits --------------------------------------------------------------------------- Entry Parameters: DE = value 0 ... FFFF Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This routine prints the DE register pair as a decimal number of up to 5 digits (depending on LZB mode). This function is used in exactly the same way as PHDE. 2.7.10. PSHDE - Print signed hex DE --------------------------------------------------------------------------- Entry Parameters: DE = value 0 ... FFFF Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function prints a SIGNED number that is in DE. The signed number is assumed to be in 2's complement form. If the number is negative then a leading '-' sign is printed before any digits. If positive then the number is printed normally. One major drawback of this routine is that any leading zero blanking that is in effect may easily look terrible when a leading minus sign is printed. If stan dard LZB or NOLZB is in effect then negative numbers look O.K. This function is called exactly the same way as PHDE. 2.7.11. PSDDE - Print signed decimal DE --------------------------------------------------------------------------- Entry Parameters: DE = value 0 ... FFFF Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This function prints a signed decimal number in DE. All operation is exactly the same as for the hex version of this (pshde) including calling parameters. 2.8. Conversion Functions ========================= The following functions perform (usually) numeric conversions from either regis ter to register or register to/from memory. These functions provide the program mer with the means of converting numbers to different bases or converting numbers to ascii character strings and vice - versa. 2.9. Standard CP/M Console Buffer ================================= Some of the routines in this section use memory laid out in a special but quite standard manner. This layout is used for reading the console into and is called a 'STANDARD CP/M CONSOLE BUFFER'. A standard console buffer is used as a data area for (usually) reading the stan dard CP/M console into. It is a data receptor. A standard CP/M console buffer is defined by the originators of CP/M as a sec tion of memory partitioned as below. buff: db max,len,0,0,0,0,0,0,0 The byte 'max' is equated to be the size of the data area. This is used by rou tines which fill the data area so that they know when to stop reading the con sole into the data area. This byte is not altered by any routines, it is only read. The 'len' byte is filled in by the routine which reads characters into the data area and indicates the number of bytes that have been actually read into the data area. The string of '0' characters is merely the data area. This should be of (max) bytes in length. In the above example, max can be assumed to be 7 since there are seven data bytes provided for the console. 2.9.1. ATOASC - Convert accumulator to ascii --------------------------------------------------------------------------- Entry Parameters: A = value 0 ... FF Returned Parameters: H = Ascii value of high nibble of A L = Ascii value of low nibble of A Registers Affected: HL --------------------------------------------------------------------------- This function converts the hex number in the accumulator into 2 ascii characters in the HL register. This is handy for printing numeric values and is used in some of the numeric printing routines described above. An example follows. start: lda value ; Load a digit call atoasc ; Convert to ascii mov a,h ; High ascii character call dispatch ; Print it now. mov a,l ; Low nibble call dispatch ; Print it too ; 2.9.2. ATOHEX - Convert accumulator to hex --------------------------------------------------------------------------- Entry Parameters: A = Ascii character Returned Parameters: A = Hex digit in lower nibble Registers Affected: A--------------------------------------------------------------------------- This function converts the ascii character in the accumulator to a hex single digit in the accumulator lower nibble. Note that the ascii character must be able to be converted to an ascii digit else this routine will return the accumu lator equal to 0ffh which implies an error condition. This is shown in the exam ple. start: lda value call atohex ; Do the conversion cpi 0ffh ; Error ? jz ascii$error ; Jump to an error handler ; 2.9.3. ASCBCD - Convert ascii bcd memory to hex --------------------------------------------------------------------------- Entry Parameters: DE -> BCD digits in memory Returned Parameters: HL = hex value of the BCD digits Registers Affected: DE, HL, A --------------------------------------------------------------------------- This routine converts an ascii string stored in memory which is assumed to be in BCD format and returns it in the HL register pair as a hex number. The ascii string may be terminated by any character that is not in the range 0..9. This routine will keep converting ascii digits till it encounters such a character. This function is very useful for extracting numbers from console buffers. Since the number is returned converted into hex in the HL register then it is imme diately usable by any of the numeric routines. On exit the DE register points to the character that was not convertible into a bcd digit (the terminator). The user is able to use this to check for illegal input. An example of this follows. ; ; Define a standard CP/M console buffer with a terminator after ; it in case the user enters a buffer full of digits. ; buff: db 5,0,0,0,0,0,0 ; 5 character buffer db 0dh ; Endline Terminator ; ; Read the console into the buffer then get in hex form into HL. ; start: call inline ; Prompt the user. db 0db,0ah,'Enter a decimal number $' lxi d,buff call cbuff ; Read console buffer lxi d,cbuff ; Point to the digits call ascbcd ; Read the number into HL ; ; Here the number is converted into HEX and returned in HL. ; 2.9.4. ASCHEX - Convert ascii memory to hex --------------------------------------------------------------------------- Entry Parameters: DE -> ascii characters in memory Returned Parameters: HL = hex value of the HEX characters Registers Affected: DE, HL, A --------------------------------------------------------------------------- This routine will convert an ascii string of bytes that is a hex number, pointed to by DE, into a hex number returned in the HL register pair. The conversion of ascii to hex will continue till a non ascii character capable of being converted to ascii is found. The character that caused aschex to terminate is returned in the accumulator to that the programmer can easily test what character caused the end of numeric input. This is shown in the example. buff: db 5,0,0,0,0,0,0 ; Standard CP/M console buffer db 00 ; Ensures a terminator for atoasc ; start: lxi d,buff ; Point to the buffer call cbuff ; Fill from the keyboard ; Now convert the buffer to a number lxi d,buff+2 ; Point to the text in the buffer call aschex ; Convert into HL ; The terminator character is in the accumulator now cpi 00 ; Blank end ? jz continue cpi 'H' ; a hex number ? jz continue ; etc...etc...etc...etc 2.9.5. HEXASC - Convert hex to ascii --------------------------------------------------------------------------- Entry Parameters: DE is converted to bcd and put at m(HL) Returned Parameters: m(HL) contains 4 ascii bcd characters Registers Affected: DE, HL, A --------------------------------------------------------------------------- This function takes the hex number in DE and converts it into 4 ascii characters which are stored in the memory address pointed to by HL. This is shown below. buff: db 00,00,00,00 ; start: lxi h,buff ; Point to a buffer lded value ; Get a 16 bit value call hexasc ; ; Now the buffer should contain 4 ascii characters. 2.9.6. HEXBCD - Convert hex to bcd --------------------------------------------------------------------------- Entry Parameters: DE is converted to bcd and put at m(HL) Returned Parameters: m(HL) contains 4 bcd digits Registers Affected: DE, HL, A --------------------------------------------------------------------------- This routine converts a hex number in DE into 5 BCD digits stored in memory pointed to by HL. This function is extremely useful for converting from hex to decimal. This is shown in the example. buffer: db 0,0,0,0,0 ; Save converted number here ; start: lded value ; Get a 16 bit value lxi h,buffer ; Point to some storage call hexbcd ; ; Now the buffer should contain 5 bcd digits -> by HL This function leaves bcd digits in memory in a slightly different manner to some other converters. The exact format is shown below. Note that changing this for mat will require modifying other routines in ASMLIB since they depend on HEXBCD for printing hex numbers as decimal etc. BCD Number Storage Format HL + 0 HL + 1 HL + 2 <- memory address |---|---| |---|---| |---|---| | 2 | 1 | | 4 | 3 | | 6 | 5 | |---|---| |---|---| |---|---| ^ ^ ^ ^ ^ ^ | | | | | `------- 10,000's digit | | | | `-----------100,000's digit | | | `---------------------100's digit | | `-----------------------1,000's digit | `---------------------------------1's digit `------------------------------------10's digit 2.9.7. NIBASC - Convert A to ascii --------------------------------------------------------------------------- Entry Parameters: A low nibble Returned Parameters: A = ascii character Registers Affected: A --------------------------------------------------------------------------- This function converts the low nibble of the accumulator to an ascii character returned in the accumulator. This is handy for printing numbers stored as bcd or hex numbers. This is shown below. start: lda value ; Load a number call nibasc ; Convert low nibble to ascii call dispatch ; Print result ; 2.9.8. IHHL - Input a hex number into HL --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: HL = a hex number read from the console Registers Affected: A, HL --------------------------------------------------------------------------- This function reads the console into an internal standard CP/M buffer and con verts the characters into a hex number which is returned in HL. Console input is terminated with a carriage return which can be annoying to the operator espe cially if more digits are to be entered later on the same line, and for this reason other conversion routines are available. This routine is provided for immediate console numeric input. The example below reads the console for a number then after this it prints a message and re-prints it in decimal. start: call inline db 0dh,0ah,'Enter a hex number : $' call ihhl ; Get a hex number into HL call inline db 0dh,0ah,'Your number converted to decimal is = $' xchg ; Load into DE from HL call pdde ; Print it as a decimal ; 2.9.9. IDHL - Input a decimal number into HL --------------------------------------------------------------------------- Entry Parameters: None. Returned Parameters: HL = a decimal number returned in hex read from the console Registers Affected: A, HL --------------------------------------------------------------------------- This function reads the console into an internal standard CP/M buffer and con verts the characters into a decimal number which is returned in HL. The decimal number that is stored in DE is actually saved in hex format, the ascii charac ters are assumed to be in decimal format. This has been done since most arithme tic and printing in ASMLIB is performed on hex numbers. Console input is termi nated with a carriage return and the last character converted is returned in the accumulator. The following example reads a decimal number then re-prints it as a hex number. start: call inline db 0dh,0ah,'Enter a decimal Number : $' call idhl ; read a decimal call inline db 0dh,0ah,'Your number converted to hex is = $' xchg ; load HL into DE call phde ; print as hex ; 2.9.10. CAPS - Capitalize the accumulator --------------------------------------------------------------------------- Entry Parameters: A = ascii character Returned Parameters: A = an upper case version of the input Registers Affected: A --------------------------------------------------------------------------- This function expects the accumulator to contain an ascii character (alphabetic) which this routine will convert to be upper case and return in the accumulator. An example follows. mvi a,'z' call caps ; Now the accumulator contains 'A' This function is extremely simple and useful for single entry console input conversion so that menu item selections can be entered in both upper and lower case. It is used in all the distribution programs for this purpose. 2.9.11. CAPSBUF - Capitalize a console buffer --------------------------------------------------------------------------- Entry Parameters: DE -> Console buffer Returned Parameters: mb(DE) is converted to upper case Registers Affected: None. --------------------------------------------------------------------------- This routine takes the address of a standard CP/M console buffer and capitalizes the alphabetic characters in it. This command is used as follows. lxi d,buff ; Point to the buffer call capsbuf ; Capitalize the buffer jmp continue ; Do the rest of the program ; ; Below is the console buffer (before being capitalized). ; buff db 10,5,'Smith',0,0,0,0,0 ; This function is used in the numeric input routines which capitalize their buf fers before progressing with their conversions. 2.10. Maths Routines ======================== The maths routines presented here are somewhat elementary considering that they only provide for integers. One other thing that may speed up the inclusion of suitable routines is the availability of good software. The user group does contain a number of floating point routines and basic interpreters all of which may be suitable, but none of them are really good or fast. Added pages to the end of this manual may tell a different story if some good code can be found or written. Signed Numbers Representation A signed integer number is represented in this software as 16 bit signed two's complement with the most significant bit (msb) representing the sign of the number. If this bit is a '1' then the number is negative else it is positive. Since the number is in two's complement form it is very easy to perform simple addition and subtraction on. The following table shows the representations of 4 bit signed numbers. Signed Number Representations Binary Bit string Decimal Value -------------------------------------- 0011 +3 0010 +2 0001 +1 0000 00 1111 -1 1110 -2 1101 -3 To convert to or from a negative signed number, invert all bits in the number then add 1. This is how comp2s does it also. In the above, the positive numbers are represented unchanged, only the negative numbers are complemented. In the ASMLIB signed printing routines the top bit is tested and if set the number is known to be negative so it is 2's complemented and printed as a negative. Positive numbers are printed unchanged. 2.11.1. MULDH - Multiply --------------------------------------------------------------------------- Entry Parameters: DE = 16 bit number HL = 16 bit number Returned Parameters: DE = Upper 16 bits of the multiply HL = Lower 16 bits of the multiply Registers Affected: A, DE, HL --------------------------------------------------------------------------- This routine does an unsigned multiply according to DE HL = DE * Hl. If the result is less than or equal to 16 bits then de is returned as 00. An example is shown below which reads the console for 2 hex numbers which it multiplies to form a 32 bit result. start: call inline db 'Enter number 1 $' call ihhl ; get a hex number xchg ; save in DE call inline db 0dh,0ah,'Enter number 2 $' call ihhl call muldh ; multiply ; Now DEHL = DE * HL 2.11.2. DIVDH - Divide --------------------------------------------------------------------------- Entry Parameters: DE = 16 bit number HL = 16 bit number Returned Parameters: HL = Reminder of dividing DE by HL DE = Quotient of dividing DE by HL Registers Affected: A, DE, HL, PSW --------------------------------------------------------------------------- This function divides DE by HL and returns the quotient in HL with any remainder in DE. The following sample reads the console for 2 numbers then divides the first by the second. start: call inline db 'Enter number dividend $' call ihhl ; get a hex number xchg ; save in DE call inline db 0dh,0ah,'Enter divisor $' call ihhl call divdh ; do the divide ; Now HL = DE / HL remainder in DE 2.11.3. SQRT - Square root --------------------------------------------------------------------------- Entry Parameters: DE = 16 bit number Returned Parameters: HL = square root of DE Registers Affected A, HL --------------------------------------------------------------------------- SQRT takes the square root of a 16 bit number in DE and returns the result in HL. This is done with Newtons approximation method which 'zooms in' on the square root of a number by successively making guesses and subtracting error values. The following example reads the console for a decimal number then prints a square root for it. Note that this routine returns an integer so that in most cases it will never find an exact root since in the majority of cases this re quires a floating point number. start: call inline db 'Enter a number $' call idhl ; Read a decimal xchg ; Load into DE call sqrt call inline db 0dh,0ah,'Its root is $' xchg ; Load root into DE call pdde ; Print as decimal ; 2.11.4. COMDH - Compare HL to DE --------------------------------------------------------------------------- Entry Parameters: DE = 16 bit value HL = 16 bit value Returned Parameters: CARRY an ZERO flags only Registers Affected: A --------------------------------------------------------------------------- This function compares the value of HL to the value of DE. The Z-80 flags return the results of this test with the ZERO flag always indicating equality, CARRY flag indicating unsigned and SIGN flag indicating unsigned size relationships. The following table illustrates the flags to be expected from this function. 16 Bit Compare Flags DE HL Zero Carry Sign ----------------------------------- Unsigned 0100 0200 0 1 1 0200 0200 1 0 0 0200 0300 0 1 0 Signed 8100 0200 0 0 1 8100 8100 1 0 0 8100 8200 0 1 0 The following example shows the console being read for two numbers which are tested for equality and the results of the test causing conditional jumps. start: call inline db 'Enter Number 1 $' call ihhl ; Input a number xchg ; Save in DE call inline db 0dh,0ah,'Enter number 2 $' call ihhl ; Get another ; Now test call comdh jz equal ; Display 'they are equal' jc 2big1 ; Number 2 > number 1 ; Here display the message that number 1 is bigger than number 2. ; 2.11.5. COMP2S - Return the 2's complement --------------------------------------------------------------------------- Entry Parameters: DE = 16 bit value Returned Parameters: HL = twos' complement of DE Registers Affected: HL --------------------------------------------------------------------------- This function converts the 16 bit number in DE into a twos' complement number in HL. This is used in signed arithmetic. Using this function is shown below. start: lded value16 ; Get the 16 bit value. call comp2s ; Convert xchg ; Load result into DE from HL call psdde ; Print as a signed number ; 2.11.6. SGNADD - Signed add HL to DE --------------------------------------------------------------------------- Entry Parameters: DE = 15 bit value HL = 15 bit value Returned Parameters: HL = addition of HL to DE Registers Affected: HL --------------------------------------------------------------------------- This function returns the signed addition of HL to DE in the HL register pair. The numbers are assumed to be standard 16 bit twos complement numbers in the range 7fffh to -8000h. This is the standard range for most compiled languages that support single precision signed integers. This function is used as follows. start: lhld value1 ; Load hl lded value2 ; Load second value call sgnadd ; Add them together xchg ; Ready the result call psdde ; Print as a signed decimal ; 2.11.7. SGNSUB - Signed subtract HL from DE --------------------------------------------------------------------------- Entry Parameters: DE = 15 bit value HL = 15 bit value Returned Parameters: HL = addition of HL to DE Registers Affected: HL --------------------------------------------------------------------------- This function subtracts HL from DE and puts the result into HL. Both HL and DE are assumed to be signed 16 bit numbers. The subtracting is performed by adding the twos complement of HL to DE which is standard signed subtraction practice. This is shown below. start: lhld value1 lded value2 call sgnsub ; Do the subtract xchg ; Ready the result call psdde ; Print as signed decimal. ; 2.12. Special String Routines ================================= The following routines in ASMLIB handle standard character strings. A standard character string has a counter at the start of it which contains the number of characters in the string followed by the characters. Since the counter is a single byte, the string may be up to 255 characters long, no more. This type of string is very common and is how PL/I-80, BASIC, PASCAL etc store their strings. The standard character string is in effect a CP/M console buffer without the leading buffer maximum size byte and the CP/M console buffer is easily used for reading or writing standard character strings. Routines are also provided for moving strings too and from standard console buffers. Parameters to these functions are passed as follows and this mirrors the parame ter passing convention used in the other ASMLIB routines previously described. DE -> String1 The first string address always in DE. HL -> String2 Any second string is passed this way. B = Number of characters to add / delete / insert etc. C = An index value into a string for an insert / delete etc. On exit, only those parameters that have been passed in are expected to be modi fied except for the accumulator which is usually changed. On exit also, if the CARRY flag is set then these has been an error usually due to bad input parame ters or string sizes. It is up to the user to trap this error flag. 2.12.1. CONSTR - Concatenate strings --------------------------------------------------------------------------- Entry Parameters: DE -> string 1 HL -> string 2 Returned Parameters: CARRY flag if an error Registers Affected: A --------------------------------------------------------------------------- This function concatenates the second string onto the end of the first string. The string addresses are passed in DE and HL respectively. On exit the carry flag if set will indicate that the resultant string would have been greater than 255. In this case only enough characters to make up the 255 max are added to the end of the first string. 2.12.2. CMPSTR - Compare two strings --------------------------------------------------------------------------- Entry Parameters: DE -> string 1 HL -> string 2 Returned Parameters: ZERO flag if a match CARRY flag if a string 2 > string 1 Registers Affected: A --------------------------------------------------------------------------- This routine tests two strings for equality. If all characters and the sizes are equal then the ZERO flag is returned. If string 2 is larger than string 1 then then the CARRY flag is returned set. 2.12.3. POSSTR - Find a substring in a string --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> substring to search for Returned Parameters: A = index position of the substring Registers Affected: A --------------------------------------------------------------------------- This routine returns the position where a substring occurs in a string or 0 if it does not occur or if either the string or substring are of zero length. 2.12.4. CPYSTR - Copy a substring from a string --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> destination (sub)string C = index to the substring to extract B = number of characters to extract A = maximum substring allowed Returned Parameters: CARRY flag if an error Registers Affected: A --------------------------------------------------------------------------- A substring can be extracted from within a string using this function. Register DE points to the string and register HL points to the destination (sub)string. Register C is the index from the start of the main string, register B is the number of bytes to copy and A is the maximum length allowed for the substring. On exit the CARRY flag will be set if an error occurred with this call. 2.12.5. DELSTR - Delete a portion of a string --------------------------------------------------------------------------- Entry Parameters: DE -> string C = index to characters to delete B = number of characters to delete Returned Parameters: CARRY flag if an error Registers Affected: A --------------------------------------------------------------------------- A substring within a string can be deleted and characters around it crushed up using this routine. On entry register DE points to the string, register C is the index to the start of the characters to be deleted and register B is the number of characters to be deleted. A carry from this routine indicates a error condi tion. 2.12.6. INSSTR - Insert a substring into a string --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> substring to insert C = string index to insert substring B = maximum allowable size of result Returned Parameters: CARRY flag if an error Registers Affected: A --------------------------------------------------------------------------- A substring is inserted into a string with this routine. On entry DE points to the string, HL points to the substring, C is the index to the place where to insert the substring and register B is the maximum allowable size of the result string. A carry out of this routine also indicates an error. 2.12.7. CAPSTR - Capitalize a string --------------------------------------------------------------------------- Entry Parameters: DE -> string Returned Parameters: ms(DE) is capitalized Registers Affected: A --------------------------------------------------------------------------- Calling this routine with the address of a string in register DE causes the string to have all its alpha characters converted to capitals. This is simple and similar to the CAPSBUF routine. This also uses the CAPS module. 2.12.8. PRNSTR - Print a standard character string --------------------------------------------------------------------------- Entry Parameters: DE -> string Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This routine extracts the size of the string, puts it into register B then uses the PCOUNT routine to print a character string. Register B is preserved through this call. 2.12.9. BUFSTR - Transfer a buffer to a character string --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> console buffer B = maximum number of characters to transfer Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- A standard console buffer is transferred to a character string with this rou tine. This allows console buffers to be used for reading characters into the moving the resultant text into a characters string (which is a buffer without a buffer size byte). One major complication here is that the string may not be large enough to hold the console buffer and to this end register B is used to contain the maximum allowable characters that may be shifted and this may be less than the size of the console buffer. 2.12.10. STRBUF - Transfer a string to a console buffer --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> console buffer B = maximum number of characters to transfer Returned Parameters: CARRY if an error Registers Affected: A --------------------------------------------------------------------------- This performs the opposite function to the BUFSTR routine. This routine sends a character string into a standard console buffer. It checks if the console buffer can contain the string, and if not only sends enough characters to fill it. Error conditions are flagged by the carry bit set. On entry register DE points to the string and register HL points to the console buffer. 2.12.11. ELBSTR - Eliminate leading blanks --------------------------------------------------------------------------- Entry Parameters: DE -> string Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This function is called with register DE pointing to a standard characters string. On exit the string will have had any leading blanks in it removed and the total length of the string shortened appropriately. The shortening of the string is done by moving the rest of the string back till there are no leading blanks. This function is very handy is command line interpreters and is used in some of the examples for this. 2.12.12. ETBSTR - Eliminate trailing blanks --------------------------------------------------------------------------- Entry Parameters: DE -> string Returned Parameters: None. Registers Affected: A --------------------------------------------------------------------------- This function eliminates any trailing blanks from a string and adjusts the length byte to suit this. On entry register DE should point to the start of the string. 2.13. Token Processing Routines =================================== The following few routines use or operate upon characters grouped into clumps which shall be called TOKENS. The token is delimited by either defined charac ters or by the start or end of the string. The central idea behind the token is that it represents something in the real world or some action that is to be taken. A token shall be defined here as a sequence of bytes (non-blanks) which are delimited by a pre-defined delimiter. The string of bytes may be in any sequence and have any length or meaning. Tokens are used to represent something else or some action. An example of some tokens would be 'PRINT', '*', 'GOSUB'. These routines were written to allow the programmer to quickly use the token approach to line input processing. The idea here is to ask the user for a line of input then to use the token routines to extract the meaning of the data. In this way, command line processors are easily built. An example of a line input processor is the following input line which is broken up by successive calls to gettok and processed using successive calls to tabsrc. line: db 11,'T0;S1;R;D;X' The first call to gettok would return a string 3,'T0;' (assuming the delimiters are set up correctly) which would be processed by using a look-up table via the tabsrc routine. The second call to gettok would return 3,'S1;'. A complete prog ram that uses token processing to strip apart a command line and do some actions upon it to drive a plotter is included on the distribution disk. This program called token has a menu facility and is fairly easy to use. 2.13.1. GETTOK - Get a token from a string --------------------------------------------------------------------------- Entry Parameters: DE -> string HL -> string to receive a token B = maximum token length allowable Returned Parameters: CARRY flag if an error Registers Affected: A --------------------------------------------------------------------------- This function extracts the first token from a string and shortens the string accordingly. Any leading blanks in the string are ignored. On entry DE points to the string, register HL points to a string capable of holding the token and register B contains the maximum token length allowable. Before this function is used the user MUST have used the DEFDEL to define the delimiters for the token. If this is not done or a register set up error has occurred then the carry flag will be returned set and no other action will have been taken. This function has been written to allow the programmer to strip strings of text apart very easily and to decode them at a later date. An example of a before and after of a token extract is shown below. The string DELIMS contains all the delimiters to that signal the end of a token. ; Data delims: db 9,'= */+-^()' ; Delimiter string: db 12,'print(45*69)' ; the string token: db 00,' ' ; Result data area start: lxi d,delims call defdel ; define delimiters lxi d,string ; source of tokens lxi h,token ; result area call gettok ; extract the first token ; ; after this call the data areas should look like this ; ;string: db 7,'(45*69)';token: db 5,'print' ; 2.13.2. DEFDEL - Define token delimiters --------------------------------------------------------------------------- Entry Parameters: DE -> token delimiter table Returned Parameters: None. Registers Affected: None. --------------------------------------------------------------------------- This routine saves the address passed in register DE and uses it to point to string which contains all the delimiters that are allowed to cease assembly of a token from the start of a string. In the above example the delimiters were the characters that signalled the end of the print statement. This function is quite critical for the proper use of the token processing routines since this function defines how a token is extracted from the string. 2.13.3. TABSRC - Search a table --------------------------------------------------------------------------- Entry Parameters: DE -> token string HL -> Table of all possible tokens Returned Parameters: A = token number matched in the table Registers Affected: A, DE, HL, BC --------------------------------------------------------------------------- This function allows the user to search a table of strings for a matching string. This is extremely useful for interpreting what the strings of text are to do. In the above example the token extracted would be used to search inside a table and the appropriate routine would be identified for the token. On entry register DE points to the string and register HL points to the table of strings. The end of the table of strings is indicated by a string of zero length. On exit the accumulator will have a number in it which if zero indicates that the string was not found in the table else this will be the number of the string which matched the string pointed to by HL. If the first string in the table matches then an 01 is returned in A etc. The format of the string table is as shown below. In order to speed this routine up as much as possible, IT IS ASSUMED BY THIS ROUTINE THAT THE TABLE IS IN ALPHABETIC ORDER. This speeds up the matching pro cess considerably and is not an unreasonable thing to ask of the programmer. If the table is not in alphabetic order then it is probable that the string search will fail on occasion and produce spurious results. stable: db 5,'PRINT' db 4,'SQRT' db 5,'SQUARE' ; In alphabetic order db 0 ; end of table ; 3. ASMLIB Organization ************************ ASMLIB (source code too) is divided into modules which contain from 1 to many routines. The modules have been put together by the LIB librarian program to form the ASMLIB library. The method of creating the library from its modules is dealt with later, but it must be understood that when a module is loaded, ALL routines in that module are included even if only one of them is declared as external. This is one of the reasons why the source of ASMLIB is split up over so many files, it helps to keep code overheads down. 3.1. ASMLIB Module Dependencies =============================== Some of the routines in ASMLIB depend on other routines (subroutines) and for this reason, it is necessary to declare all the required routines as external when writing a program. The following table lists all routines in ASMLIB and the modules / files they are in and also all the dependent routines. This is almost essential when writing a program. Function Module Dependencies Code/Data ----------------------------------------------------------------- ADDCRC CRC NONE ASCBCD ASCBCD CAPS 046/02 ATOASC ATOASC NIBASC 00F/00 ATODIN ATODIN NONE 03C/00 ATOHEX ATOHEX CAPS 01B/00 BCDHEX BCDHEX NONE 02D/00 BELL SCREEN DISPATCH,COE BLZB SETLZB PROLOG BUFSTR BUFSTR NONE 00D/00 CAPS CAPS NONE 008/00 CAPSBUF CAPSBUF NONE 010/00 CAPSTR CAPSTR CAPS 00C/00 CBUFF CBUFF CIE,COE CHKRNG CHKRNG NONE 02F/01 CHKTBL CHKRNG CHKRNG CIE CPMIO NONE CLEAR SCREEN DISPATCH,COE 036/00 CLEOL SCREEN COE CLEOP SCREEN NONE CLKRD CLOCK NONE 083/00 CLKWR CLOCK NONE CLRCRC CRC NONE 02D/02 CLZB SETLZB PROLOG CMPSTR CMPSTR NONE 023/02 COE CPMIO NONE COMDH COMDH NONE 00A/00 COMP2S SMATH NONE 014/00 CONSOUT SWITCHIO PROLOG CONSTR CONSTR NONE 052/05 CPYSTR CPYSTR NONE 049/02 CRLF SCREEN DISPATCH,COE CST CPMIO NONE CUROFF SCREEN COE CURON SCREEN COE CURSOR SCREEN COE DEFTOK TOKEN GETTOK,DELSTR,CMPSTR DELAY DELAY NONE 015/00 DELSTR DELSTR NONE 03D/01DISPATCH DISPATCH PROLOG,COE,CIE,LOE DIVDH DIVDH NONE 038/00 ECHOLST SWITCHIO PROLOG 00D/00 ELBSTR ELBSTR DELSTR ETBSTR ETBSTR DELSTR FLUSHLOE DISPATCH PROLOG FORMIN FORMIN CIE,COE 18A/10 GETCRC CRC NONE GETTOK TOKEN DELSTR,CMPSTR GETYORN GETYORN CIE,COE,DISPATCH,CAPS HEXASC HEXASC ATOASC 013/00 HEXBCD HEXBCD NONE 02D/00 HLP HLP * ** IDHL IHL CBUFF,COE,CIE IHHL IHL CBUFF,COE,CIE 080/0B INLINE INLINE DISPATCH 00D/00 INSSTR INSSTR NONE 05A/01 IONUM CPMIO NONE LISTOUT SWITCHIO PROLOG,LOE LOE CPMIO NONE LST CPMIO NONE LZB SETLZB PROLOG 071/01 MON MON CIE,COE,CST,CLKRD,CLKWR 58E/33 MULDH MULDH NONE 02D/00 NIBASC PACC NONE NOLZB SETLZB PROLOG PCOUNT PCOUNT DISPATCH,COE 00A/00 PDACC PACC PROLOG,DISPATCH,SETLZB,HEXBCD,COE,LOE PDDE PDE PDACC PHACC PACC PROLOG,DISPATCH,LZB 046/00 PHDE PDE PHACC 048/00 PMENU PMENU DISPATCH,XYPSTRING 009/00 PORTSET PORTSET NONE 010/00 POSSTR POSSTR NONE 047/07 PRNSTR PRNSTR PCOUNT 008/00 PROLOG PROLOG NONE 01E/91 PSDDE PSGN PDDE PSHDE PSGN PHDE 01D/00 PSTR PSTR DISPATCH,COE 008/00 PSTRING PSTRING DISPATCH 00C/00 QUIT PROLOG PROLOG RAMBPT RAMBPT NONE 0B4/00 RAMWBT RAMWBT NONE 056/00 RANDINIT RANDOM NONE RAND8 RANDOM NONE RAND16 RANDOM RAND8 RANDP16 RANDOM RAND8 SETATT SETATT COE 08E/01 SETXY SCREEN COE SGNADD SMATH NONE SGNSUB SMATH NONE SQRT SQRT NONE 039/00 STRBUF STRBUF NONE 00E/00 TABSRC TABSRC NONE TNAME SCREEN NONE USERIO SWITCHIO PROLOG VERSION PROLOG NONE XYINLINE XYPRINT DISPATCH,COE,SETXY XYPSTRING XYPRINT DISPATCH,COE,SETXY 024/00 Notes: A module has its code included the first time it is used (called). The only overheads after that are the 3 bytes for the call instruction. * A routine separate from ASMLIB, but supplied on the distribution disk in both source and .REL form. ** No size available since this file is meant to be user modified to suit an application. In the above table, the module gives the name of the assembly language file that the function is found in and also is the same name as the module in the library itself. The name of the module is helpful as it shows what will be loaded into memory when ANY item from the module is used. The above table has been under intense revision as the source code modules have been re-organized and so unfortunately the module may differ marginally from the above in both space and dependencies. The Code/Data column gives the number of bytes of code & data in the module. This number of bytes is loaded in at link time when the application program is linked with ASMLIB. If a module is used more than once then the only code over head is the assembly language call instruction, the library module is NOT loaded again into the users program. The number of bytes displayed for the module is given only once in the table for the module. 4. Using ASMLIB Routines ************************** Since all the functions in ASMLIB are called in a very similar and standard manner, ASMLIB is a snap to use. In general, the process of generating an assem bly language program for using ASMLIB routines involves three simple steps. These are. 4.1. Writing The Assembly Language Program ========================================== The assembly language program is written using an editor such as Wordmaster or Vedit or Mince or whatever. The assembly language program can be called by any name must be of type '.ASM' to be recognized in later steps by subsequent prog rams. The manual on your particular editor will have to be consulted for instructions on how to edit a file. This step of the process in programming will become auto matic after some experience (as will the next steps). At the start of your program you must tell subsequent sections of the program what pieces of code are to loaded from external sources (ie. from ASMLIB) and this is done by declaring the external code as EXTRN. This is a directive to a subsequent program (and the assembler) that the thing that has been declared as external will be defined in another program or library or somewhere. An example of a complete assembly language program to clear the screen and print a simple message follows. A word of advice at this is that it is strongly recom mended that all assembly language programs are commented and the more complex ones should be documented. If this is not done then at some later time someone is sure to waste time in deciding what the program was for and how it does it. This is done in the following program at the top of the file (even in a simple program like this). ; ; Start of a simple test program. Clear the screen then print a ; message . ; extrn prolog,dispatch,clear,inline,coe ; ; Start the program call prolog ; Set up stack etc. call clear ; Clear the screen call inline db 0dh,0ah,'This is a simple program test$' jmp quit ; The end of the program. ; end In the above program, it started of by declaring that prolog, dispatch, clear, inline were all external. When this is done they can be referenced in the prog ram at any time or place in almost any manner. After the declarations, the program started with the call to prolog. Next came the call to the clear function which clears the screen then we called the inline function which printed the little text message following it. Lastly, so as to return to the operating system we jumped to the quit function which recovered the operating systems return address and returned straight to the CCP. Of note next is the end statement. This statement directs subsequent programs that this is the end of the assembly language file. Without this statement the program will not assemble properly. Note well that the end statement MUST have at least one carriage return after it. This is signified in the above program by the full stop a few lines further on. 4.2. Assembling the File ======================== When the source code assembly language program has been written, it must be assembled which means that it must be turned into an executable program. Before this is done, the only thing you have is a file of text that represents the instructions that you want the computer to execute, you do not yet have the instructions. The assembly (or conversion process) is very simple and quite quick. The Digital Research RMAC assembler is used to read the file and will emit a new file of type '.REL'. The following example shows the typical command that would assemble a file called myprog.asm. A>RMAC MYPROG Files of type .REL have not yet been told where they will be run and may contain references to other programs (ASMLIB in this case) which have not yet been re solved (satisfied). Files of this type also are not in ascii form so that they cannot be typed or displayed on the screen in any simple manner. At this stage you do not yet have an executable file, but are getting very close. If an error is encountered in your source code file during the assemble then an error message will be displayed. This message will indicate what the error was and will display the line of the file that caused the error. It is recommended that the manual on MAC and RMAC are consulted when an error message is display ed. Usual causes of errors in assembly programs are forgetting to declare an external function, mis-typing and instruction, giving an illegal directive to the assembler or using the same label twice. 4.3. Linking the File ===================== The last step in generating an executable file is the link process. This process reads in the .REL files that you specify and links them to form an executable end result '.COM' file. The following example links MYPROG to ASMLIB and causes only those functions that are used to be included ( using [S] causes this). If the [S] is not used then ALL the functions in ASMLIB will be loaded into the end result program which will be much larger than it needs to be. A>LINK MYPROG,ASMLIB [S] On the disk now should be a file called MYPROG.COM which can be immediately run with the command A>MYPROG. This assumes that the link process completed satisfactorily. During the link process there will be a number of messages printed on the screen. These messages show how many functions are being pulled out of ASMLIB and how big they are. One message that must be checked for is the 'UNDEFINED SYMBOL' message. This indicates that your program is trying to use something that does not exist or was not found at link time. This error message indicates that the program (usually) had an external declara tion which was not found when the linker was being run. The best way to check this is to read through the assembly language program and check all the declara tions. If the program is executed after this message has been displayed then it will probably terminate prematurely or die some other horrible death. The depen dencies section of this manual should be checked as you may be using a function in ASMLIB that requires other modules to run which have not been declared as external. The 'Undefined Symbol' message really means that the linker could not find the code that your program refers to in any of the files that were specified on the command line. Since the linker will happily link as many files together as you specify, it is a trivial matter to add other libraries of code to the link process. All you have to do is add their name on the command line and give any options (ie. [S]) that are needed. It may be a good idea to read the LINK manual before doing this though. The linker may also be used to set the origins of the program itself or the data areas that the program will use. This is a very powerful feature of the linker as it allows the user some freedom from having to reassemble a file when the program is to be run at a different address. The options for doing this are given later in the text and are described at length there, but are shown below also. To link a program for a different address... A>LINK MYPROG,ASMLIB[S,Dxxxx] would link MYPROG to ASMLIB and set the data storage areas to xxxx (in hex). To link to run at a different address, the following is used to run at address yyyy with the data again at your address of xxxx... A>LINK MYPROG,ASMLIB[S,Lyyyy,Dxxxx] 4.3.1. Using the LIB program ------------------------------- The LIB program is used to add, delete and replace modules in the ASMLIB library (or any other .REL file). LIB also has the ability to print all the names of the publics or module names in a library. This is handy as a new I/O driver module is to be loaded into the library, but the current one cannot be remembered. Some of the things that LIB will do are shown below. A>LIB ASMLIB[M] This will display all the names of all the modules in ASMLIB.REL. A>LIB ASMLIB[P] This will display all the names of all the publics in the ASMLIB.REL. A>LIB ASMLIB=ASMLIB This will replace the CPMIO module inside ASMLIB with the SBC800IO module. A>LIB ASMLIB=ASMLIB This is tricky! The SBC800IO module is identified by its unique FIRST SIX let ters and it is replaced by the CPMIO module. Giving the full name of SBC800IO causes an error since it is more than six characters long! One last thing, the LIB program is a little persnickety about spaces in the command line, future versions of this program (from Digital Research) may cure this. The above commands do work. 5. Writing Assembler Programs ******************************* Writing assembler programs can be a daunting task. Few people would argue this. The next few pages aim at setting out some of the methods that can make assem bler programming far more pleasant and far more productive. Assembler program ming is like most other things in that it is what you make of it for good or bad. There is no reason why assembler programming can be good fun if it is tack led properly. Many people have it as the language of choice, they are so used to it! 5.1. Block Structuring ====================== Since assembler coding has almost no high level constructs such as procedures and local / global data structures or parameter passing conventions, the pro grammer has open slather in what is done and where. This is where the problem arises, unless care is taken the program will degenerate into a messy thing that is bound to failure due to its gyrations through memory. One of the best methods of programming and the most widely used is that of block structuring. Block structuring involves breaking your program up into easily written-debug ged-maintained-documented self contained pieces called blocks. These blocks may be a few or hundreds of lines long. The key is to produce blocks that are bug free. An important rule to block structuring is that blocks must never communicate between each other unless world shaking things occur. It is also best for blocks to pass parameters via storage in memory (unless speed is of the essence) in stead of using registers since registers have a habit of becoming corrupted or just plain lost. Memory parameters are more easily understood at a later date when 'mods are done. The use of registers to pass parameters can be left to a last refinement of a debugged and correct program if extreme optimization is needed. Blocks of code are linked by a main supervisory section (called the main loop) of code that is responsible for calling code blocks as needed. The main section of code contains the programmers major algorithm while the blocks carry out the individual dirty parts of the program. An almost immediate advantage of the block structuring approach is that once the main loop (supervisor) is written, the blocks may be written as stubs. These stubs are used to test that the main loop works and that the algorithm may be correct. When this is achieved the stubs are individually filled out till the whole program works. This promotes a step-wise approach to the program which if done carefully can give excellent results. This all looks like common sense but it is amaaazing to see the number of prog rams that missed this simple aid. 5.2. Top Down Design ==================== Design is usually a good way to start any program and there a few good methods of design. Top down programming is possibly one of the best methods for writing good programs. It takes a little mastering and if it looks to be the method for you then there are many good books on the subject. The following paragraphs should give a taster to the top down method. Top down design calls for the programmer to generates successively detailed definitions of the problem till the method of solution is obvious. The program mer starts with the most broad definition of the problem then successively re fines the definition until the problem definition has become the problem solu tion. Top down programming is heavily dependent on the definition of the problem that is being solved by the program. It is very suitable for the block structuring method of coding which is really a simplified approach to top down programming. It is vital for the programmer to have a few clues about what the end result of the program is to be, what the inputs and outputs are to be. As each detail of the problem emerges it is split up till it can be split no more at which stage it is almost solved. This level is usually the type of level that is suitable for coding into a block of code. 5.3. Flowcharting ================= Flowcharting for assembler programs can be a real headache if approached poorly. Since assembler code does so little per instruction, the flowchart for a program can be enormously large if the programmer gets into too much detail. A flowchart is a diagram made up of boxes of various shapes which represent different program flow decisions and actions. The flow chart uses lines drawn between boxes to represent decisions or program flow path. Some boxes represent simple actions, diamond boxes represent decision places and circles usually represent a start or finish place. If the programmer uses the flowchart for deciding program flow and keeping the scope of the flowchart as wide as possible, then it will be a big help. The action of each piece of the flow chart should be described in a 'plain english' type of way so that it is easily understood, possibly in the margin of the pa per. An example of a typical flowchart decision is 'IF END OF FILE THEN...'. The flowchart can be thought of as a set of functional blocks which are linked together to form the complete program and can be used to generate code. Label ling the flowchart blocks and writing software around this has been found to be a way of writing reasonable programs. Careful you do not lose the flow chart though as it will usually be needed later for understanding the program flow. The flowchart can also be an excellent way of documenting the program. For this reason, it is common for some organizations to require flowcharts to all prog rams, and many programmers write the program first then document it with a flow chart. A disadvantage of flowcharting is that it can diverge from the main loop & block type of programming. The flowchart can generate code that ambles all over the place (from block to block rather than from main to block and back) and later on looks to be a little mis-guided. Flowcharts can possibly be best used to advan tage in small functional block design or in the definition of the program rather than its writing. With most things though, it is up to the individual to use what suits best. 5.4. Pseudo Coding ================== The pseudo coding approach is one of the latest method of coding to reach these shores from America. This method involves using a plain english style of program definition which uses constructs from a high level language (such as Pascal or PL/I) to describe program segments / decisions etc to sketch the program. After this is done in sufficient detail the programmer writes code to emulate the pseudo code statements. It is also becoming common to see programs written that include the pseudo code statement in the comment sections programs as they prog ress. This looks to be a pretty good approach to programming but again, it must be carefully done with structure firmly in mind else it is of little help. An example of a pseudo code for a program that would read a disk file and dis play it to the screen follows. This sketch is the one that is formulated in the first ten minutes of program developments and is expanded on. This sketch also gives the whole program in broad detail. dump: Declare storage: userfile characters /* filename stored here*/ data 1 record of bytes. /* read disk into this */ address 1 word /* address of file data*/ record 1 word /* file record number */ Program: get 'userfile' /* read console for filename */ open file'userfile' /* use bdos */ if error then put 'File Not Found' stop. /* exit after error message */ while (not endoffile) /* the loop to read all file */ read a record of data display address of the data in the file display file record number display data as hex display data as ascii representation update pointers & addresses end. /* file reading loop */ end dump. In the above there are some important things to observe. 1) The program is almost adequately documented by the pseudo code. Later users of the program can use this pseudo code sketch to understand the program. This is important. 2) Storage is laid out at the start of the program so that we know what we are going to be reading into and from and what variables there are to be. This can give us an idea of the processing that is going to be done. 3) Commenting is used between comment characters (/* ... */). 4) Sections of the program that are part of a conditional or a sequence con trol statement are indented so that the structure or program flow is clear ly defined. It must be obvious. 5) The parts of the program that are least known (inside the while loop) are simply described. From this it is obvious what has to be done. In a simple program like this there is little need to do a lot more to the pseudo-code sketch since the pieces in the existing sketch are quite simple. One thing to watch out for is that the pseudo coded program can diverge from a block structured approach. Even in the simple program above it is far better to put all possible code into sensible blocks which are called from within the while loop. In this manner the program can be updated more easily and debugged with more certainty. Do not get caught by the simplicity of this (or any) prog ram. What happens when we want to print the display to the printer or we want to interrogate the console for options as the lines are being printed ? These mods can be done more easily if the sections of the program are in block format and if the program is nicely documented. Do p-code sketch of the previous one in a block structured approach. 5.5. Screen model ================= The last method of program design is possibly the simplest and is used by most programmers even if done on subconscious level. This is called the SCREEN MODEL method. The Screen Model method requires the programmer to completely define all data that that is to be displayed to and accepted from the users screen. This method is best suited to small programs or program blocks and assumes a straight for ward problem. The screen method is only a way for the programmer to clearly define the program (or a segment of it) so that the program reflects the programmers understanding. Since many programs do much of their work in collecting and displaying data, it stands to reason that once the screens are worked out that the rest of the prog ram is that much easier. this can be a great aid to the block structuring of a program. For this to work, it is essential that the problem is understood and that the only hard part of the program (or segment) is what is to be read and displayed. In many programs, after the inputs and outputs have been catered for, most of the problem solving is of a trivial nature. The screen definition once under stood can be (usually quickly) implemented in software and demonstrated. A real benefit of a screen definition is that it gives the programmer a "feel" of what the program is going to do and how the users will interact with it. These are important underlying features of generating the screen first. The main advantage of the screen method is that a program can be quickly brought to a demonstration stage where dummy (or representative) data are read from the user and displayed on the screen so that an idea can be given as to weather or not the program will be acceptable. Users have a nasty habit of changing their minds and causing many headaches. The sin of omission is rife among users who declare that a program is useless unless a particular feature never before men tioned is suddenly implemented. If the screen method is used to generate the I/O screen then the user is able to determine if a data input or output is missing so that a mistake in the program (or problem) definition can be spotted before too much time is lost. The Screen Model method can be applied to other areas in a program. Suitable areas for this type of programming technique to apply are data storage and ac cessing and file storage and accessing. The Screen Method is a means of reducing the size or difficulty of a program down (a subset of block structuring) so that the program becomes more easily written and maintained. The programmer is also able to get a good insight into the large scale aspects of a program using this method. 5.6. IN THE LONG RUN ==================== In the long run, all points considered, weighing all alternatives, there is possibly no single approach to programming that suits the reader. To this end the reader is left to do whatever is required to write good programs, possibly use a mixture of all approaches (most people do) or visit a bookshop and buy up big and do the reading. Rest assured that there is no easy way, you will have to work at it and continue till your skill level is such that programs fall from your fingers. Experience has show that as ASMLIB was written the whole job of programming is made easier when there is a known goal and when a block structured approach is used. Bugs in ASMLIB were found to have arisen from departing from the block approach. ASMLIB is a group of small modules (blocks) which are lumped together after assembly. Due to the block approach and with a little care there is no chance of intermodule corruption and only marginal chance of a module failing. Planning is the key here. To the employer, the ultimate goal is to have programmers who produce infinitely good programs in zero time and to this end, any improvement that is made in efficiency must be grasped firmly. The employer will not be too happy with the unlucky person who does not understand his own program a month later or spends too long on a simple job. 5.7. Programming Style Summary ============================== The following table attempts to give an idea of the relative merits of different methods of designing and writing programs. The higher the score the better. Situation A is a simple small scale program which may be up to 80k of source code to produce up to 4k of executable code. Situation B is much larger program that may have to hand data to other programs, handle disk files, have interac tive screens or generally be better flash. These two generalizations, while being a little gross, hopefully cover a broad spectrum . Note that the table really only gives an indication of a biased view of the best ways of writing programs. There are some broad assumptions that must be stated before the table is viewed. 1) The table, as a whole, is only meant to to give a "gut feeling" or broad outline of where some style shine and others do not. The figures are meant to indicate that one method is preferred, will be easier, give better re sults that another, etc. The figures are a guide, change them if you do not like them! 2) All figures assume total access to any notes, flowcharts, step charts and documentation made when a program is written. 3) The total figure is just a summary of where a style of programming fits. It is not a math average. 4) Debugging is getting the program to work, filling in missing bits of code and correcting mistakes. Debugging ease also includes the ability of the program to stay debugged so that fixing a bug does not introduce other bugs. 5) Testing is running the program with data to see that it works, and being sure that it does work. 6) Maintenance is the relative ease of doing later work to the program to add, fix or re-model it in some manner.7) Reliability is the ability of the program to protect itself from the user, the ability to keep on working, the ability to give correct results. 8) Demo. Ease is the time it takes to get the program doing something that the end user can see. It is a measure of the ease with which a programmer can get a program to a simple "stub" (non-functional) level. 9) The scores for the two situations assume that that the difficulty of a program increases linearly with its size. This is to imply that for a prog ram twice the size, it should be approximately twice as difficult to come to grips with than a smaller program. For two scores to be the same for the two situations, it is assumed that a linear increase in effort (or what ever) will occur. Table 5-1: Programming Methods Table +---------------+-----------+-----------+-----------+-----------+-----------+ | | Block St' | Top Down | Flowch' | P-Code | Screen | +---------------+-----------+-----------+-----------+-----------+-----------+ |Situation | A B | A B | A B | A B | A B | +---------------+-----------+-----------+-----------+-----------+-----------+ |Ease of use | 10 9 | 10 10 | 8 5 | 8 8 | 9 6 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Speed of use | 10 9 | 9 9 | 7 4 | 8 8 | 8 8 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Debugging ease | 10 10 | 9 8 | 7 6 | 7 6 | 7 7 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Testing ease | 10 10 | 8 7 | 7 7 | 7 6 | 7 7 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Maintenance | 10 10 | 8 7 | 7 5 | 7 6 | 8 7 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Reliability | 10 10 | 10 9 | 7 7 | 7 7 | 9 9 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Code tightness | 7 7 | 9 9 | 10 10 | 8 8 | 7 7 | +---------------+-----------+-----------+-----------+-----------+-----------+ |Demo ease | 9 9 | 8 7 | 5 4 | 6 5 | 9 9 | +---------------+-----------+-----------+-----------+-----------+-----------+ |General total | 10 10 | 9 9 | 7 7 | 7 7 | 8 8 | +---------------+-----------+-----------+-----------+-----------+-----------+ 5.8. Testing ============ When your program is designed and written you will be wanting to use it. Unfor tunately programmers have been known to make mistakes and this is where the pleasure of testing and debugging starts. Testing of your program may be a highly involved process which involves may inputs and outputs or your program may be a simple thing to test. Either way, your program should be fully evaluated to see weather or not it has a problem in some area. The following points should be considered when testing a program. The initial tests of a program should be with data that has a known result. If correct results are given for some inputs then this means that there are some sections of the program that work. This can also mean that you picked luck inputs. Keep testing! Boundary condition data should be input to the program next. This data should test all conditional branches and loops. The data should represent the most wild possible input possible such as a pay cheque over 6 figures or two people having the same name or the year going from 99 to 00. Boun dary data can show up some nice problems. Where possible the program should be tested with bad data. This is data that is not in the correct format for the program to evaluate. This will test how defensive the programmer was and how good the anticipation for unfriendly users was. Examples of bad data are entering alpha characters instead of numerics or upper/lower case mixing to responses, zero divide, overflow and other things. Look for input conditions that show up an error in the main loop algorithm. Distinguish very carefully between an algorithm problem and an execution / coding problem. There is no use modifying a programs' body when the head is at fault. This is important. During the testing phase the programmer should not stop when one fault is obser ved. The programmer should try to make the program fail in as many ways as pos sible so as to anticipate any possible situation. The programmer must end up with a list of things to fix. As the faults are observed it may be a good idea to document where they might be and to possibly write down the area in the program where the fault occurred. In the heat of modifying the program later you may not consider all the possibili ties. 5.9. Debugging ============== After all known faults in the program have been observed, the programmer is in a position to start fixing them. This is called debugging. The debugging time of a program is usually related to the design and method of writing as mush as to the complexity of the program. When a nicely laid out program is being debugged, the programmer will have a clear sequence of logically related parts to test. When the parts are correct then the whole program should be true and correct. The simple concept of block structuring usually leads to easier debugging due to its modular nature and the hopefully well laid out manner of the program. With a block structured program, the writer should be confident that when a problem is fixed in one block that it does not cause another block to malfunction, and this is part of the reason that block structure is harped on. As the program was being written, the programmer will probably have written the main loop first and tested it so that it looked correct. The blocks at this stage may have only been stubs which displayed a simple message or did a elemen tary function. As the blocks were expanded out they should have been tested and debugged individually. When all the coding was completed the programmer is pro bably in a position to do the only minimal debugging which will probably be due to a departure from a block structured program or an erroneous main loop. Debugging of a block structured program may be eased by considering the follow ing things. Firstly, the main loop and the individual blocks may be tested as separate entities for all possible inputs and their responses checked. Since the main loop contains the linkage to all the blocks and is the algorithm of the program, it is not a trivial thing to modify the main loop. Before this is done, the programmer must know for certain that the main loop / algo rithm is at fault which may be done by checking the list of faults. When all the blocks and the main loop are tested then it is hopeful that the whole program is functional. Avoid making mods to the basic idea behind the program till it is debugged finally (ie. Don't change course mid stream). There is the constant tempta tion to add nice bits to a program as it is being debugged. The end result of these ad-hoc additions will usually be an ad-hoc program that is not well designed and more difficult to debug than need be. Never make a modification to the program that diverges from the block structured approach. Blocks should not be allowed to cause each other to crash. It must be impossible for one block to adversely influence the pro cessing of another block. A valuable aid to debugging is to have a test mode in the program which allows the user to display diagnostic messages as the program is operating. Even when the program goes to the end user (or sold), this test mode can be left intact but with a hidden method of entry so that it is not invoked accidentally. The test mode can also have a second mode where the output goes to the printer for a permanent record (use the listout command) so that end users with exotic problems can be believed by the programmer and the diagnostic output can be used to spot the block in error. One of the best ways of implementing the test mode is to have a separate block which is called at the start and possibly also at the end of each module. When not in test mode (use a memory variable) a return is done immediately else a display of parameters / registers / memory is done. The time overhead for this can be very small and the gains well worth the effort and space. The test mode design may be enriched in many ways and possibly by using the mon function also which allows very nice search, display and modify functions on memory and I/O ports. During the debugging phase, one of the biggest helps is a listing on paper. This allows the writer to view the whole of the program and not a single page as is displayed by the editor. The listing may also be highlighted or marked where possible faults are. The listing will also provide information later on during subsequent testing that may show where a new fault was generated by an unwise modification. In summary, during the testing and debugging process the following guidelines should be kept in mind. - Find all faults during the test process. - Test the program with as many result-known inputs as are needed. - Test the program with boundary values. - Test the program with unfriendly inputs. - Try to debug the program a module at a time. - Try to have a listing at hand. - Be very careful of making changes to the algorithm. - Consider having a test mode. 5.10. Bugs ========== ASMLIB is of course supplied on an as is basis with the author not wishing to get into hot water if a module bombs out for some exotic reason. It is up to you to test any programs that use ASMLIB before placing them in risk situations. As you receive it you can be sure that all the sample programs on the disk work and that others not supplied also work. There has been a lot of work put into the test programs and into debugging in general. There is great incentive for fixing any bugs since it is so easy to eliminate problems from all existing files that use ASMLIB by re-linking to a corrected library. The idea is to have a good product with happy users. The idea is to have a good product with happy users. What the above means is that if you find a problem then the authors would like to hear about it. To make it easy, the following may help in diagnosing prob lems. 1) Make absolutely sure that it is ASMLIB at fault and not your program. If it is your program then bad luck. If it is ASMLIB then it may be worthwhile writing a simplified version of your program to pin-point the problem. Maybe a look at the sample programs may help. Tried the documentation ? The index is there for a purpose. 2) Check the version number as returned by VERSION. If in doubt of your ver sion or the current date is substantially later than that on the front of this manual then a 'call to the suppliers may get the latest version number and a report of any bug fixes admitted to. 3) Look at the exact results of the problem. This can be in the form of a printout (try the listout switch). This is most important information since it determines all further thought and conversation (ie. where the problem is). Using a debugger will be necessary if you want to look at registers used and memory variables. Possibly the MON function may help to look at memory. 4) It may be necessary for ASMLIBs' authors to peruse a copy of your source code. If this is to be sent then your program must be documented or well commented. All efforts will be made to correct the problem in ASMLIB that is causing the fault. Any update to ASMLIB will be supplied on your media. Unfortunately the authors cannot venture into the fixing of assembly lan guage programs of others. ASMLIB is our program and we can fix it, your program is your program and will be treated as such. Any modification or glaring problem in your program will be commented as an exercise in good will, not out of responsibility. Finally, do be careful before suspecting ASMLIB. There is a natural assumption by program authors that something outside of their control is causing a program to crash. The authors of ASMLIB are just the same and will not believe that a bug exists in ASMLIB until shown conclusive proof or given a set of circumstan ces to try where ASMLIB crashes. 6. Modifying the ASMLIB library ********************************* ASMLIB is a standard relocatable library of software and is totally compatible with the Digital Research linker and librarian (LINK and LIB) . Linking applica tion programs to ASMLIB has been shown, but using the librarian is now explain ed. Some (probably total) compatibility exists with the Microsoft products also, but this is up to the user to check out, the author likes Digital Research pro ducts better! On the distribution disk there are a number of submit files which are used to perform the following functions. These files were used to create ASMLIB and are expected to be used to perform modifications or additions. Note that ALL submit files expect the source of the addition to ASMLIB to be on drive B. It is expec ted that the manuals on LIB and LINK are read. It may be useful to printout the submit files on your printer for easy reference. The files are quite elementary are are used to ease the task of typing long command lines. One feature of the command lines that modify the library is that they make a copy of the library before they do any changes to it. This is a safety measure in case the operator makes an error in initiating the submit file or there is some obscure bug in some aspect of the changed library. The copy is called ASMLIB2.REL and may be used in place of ASMLIB.REL since it is a copy of the original. 6.1. Adding Functions to ASMLIB =============================== The submit file ADDLIB.SUB can be used to add a function or module to ASMLIB. This submit file is invoked as follows which will add the module called 'MYMOD' to the library. Note that the module file is expected to be on drive B. A>SUBMIT ADDLIB MYMOD Since ASMLIB is the result of linking many modules together, adding functions is highly recommended. It is also highly recommended that the simple register set ups are also adhered to. These setups, though they may not be the best for you, do have a common theme and are easy to use and understand. The whole idea about them is that they are standard. New modules, if others are to use them, should conform. 6.2. Substituting a Module ========================== To delete a module from ASMLIB and to replace it with another module of the same name from drive B can be done with the following command. The name of the module that is being replaced must exist inside ASMLIB else an error will be reported by the librarian and execution will abort. A>SUBMIT MODLIB MYMOD One major restriction here is that all module names must be unique in the first six letters. This is imposed by the librarian and linker programs which only use the first six letters in searches etc. If this is not done then errors will be reported about multiple definitions existing and the like. 6.3. Generating a new library ============================= If for some reason you have erased the ASMLIB library and you wish to regenerate it from the (separately purchased) source code files then two submit programs may be run which re-assembles all the .ASM files located on drive B and then link them to make a new ASMLIB library. The commands to do this are. A>SUBMIT MAKEASM $SZPZ <-- Re-assemble. This is slow A>SUBMIT LIBASM <-- Re-link . Quite fast The MAKEASM submit file has the ability to accept a parameter(s) which will be used in the assembly. It is strongly recommended that the above examples' $SZPZ parameters are used. These parameters cause the assembler to NOT generate print and symbol files and therefore to assemble in about half the time. Also of note is that due to the number of files on the disk it is probable that directory space will be exhausted if single sided disks are used. This will not be a prob lem on hard disks. 6.4. ASMLIB Source Code ======================= The source code for ASMLIB (purchased separately) comprises many assembly langu age files some of which contain one routine, some containing many. Since there is at least 160k of source code, reassembling and re-linking may take a little time. If you are leaving the computer while this is running, it is recommended that a printer be toggled on with a control P so that any error messages are retained for later reference. Only a few pages of printout will be output. It is probably necessary for the source code to be transferred to a higher capacity disk before attempting to reassemble else disk and directory space will not be enough on the single density supplied disk. The files have been heavily commented and documented and it is expected that this will be enough for "hackers" to effect modifications as required. 6.4.1. Hidden Functions -------------------------- The linker has ability to cause all symbol names (and public and externals) that start with a "?" to be NOT printed when the library is linked to an application program. ASMLIB has about a dozen subroutines and data bytes that use this facility so that they do not clutter up the linkage display. These can be effectively ig nored by programmers, but if required to be viewed at link time the additional option of "Q" in the link command will cause these to be displayed. This is shown below. A>LINK MYPROG,ASMLIB[S,Q] 7. Different Hardware Consideration ************************************* When ASMLIB is run in different hardware configurations or in ROM then there are some simple things to remember and a few things that must be done by the user. These are laid down in the following sections. 7.1. Data Storage Areas ======================= When PROLOG is called, a fair portion of memory is setup for the stack and for some variables used by other program modules. The stack has been set into a "db" area that is initialized with "S" characters.The present version of ASMLIB re quires 0A0h (or less) bytes of RAM to run, and 128 of these are stack space. The first bytes of data memory are reserved as stack so that the programmer can increase stack size if required. It may seem strange to make the stack "hard coded" as it is but there are good reasons. Firstly, the stack size of 64 levels was set since this is about 8 times the size of the average program, this is plenty. Initializing the stack space has been done so that the user can see (by looking at memory) how deeply the stack has progressed into stack space. This is done by setting the stack space to "S" bytes instead of the easier 00 bytes to be expected. Variable space is initialized with 00 bytes, which is expected by some of the modules. In the PROLOG module there are two public labels which may be used to determine the start and the end addresses of the data storage requirements in this module. These labels are called DATSTART and DATEND. Use of these labels is shown below where the first statement loads the address of the start of data storage and the second loads the data end. After this they are subtracted to get the total data storage used by this module (some of the other modules also use a little sto rage). start: lxi d,datstart ; ending address lxi h,datend ; end address (larger) dsbc d ; HL = end - start (= size) call inline db 0Dh,0Ah,'Prolog data size = $' xchg ; load the size into DE call phde ; print the size in hex ; ; continue or whatever ; Hard coding stack and data areas makes it possible to allocate the address for it in a LINK time, instead of assemble time, so you can shift it around in memo ry as needed in ROM applications. 7.2. Running in ROM =================== All of the ASMLIB routines that have variable states (or switches such as user io, listout, etc.) and use variable storage space require RAM storage space. The address of this space is allocated at link time and MUST reside in R/W memory. This is done using the linker with its data origin switch. The example link that follows shows MYPROG being linked to run at C000h with its data areas loaded at F400h. It is also necessary to make the linker understand that it does not need to pad result disk files with 00 bytes to make up the address to be linked for. This is done using the link address switch. A>LINK MYPROG,ASMLIB[S,LC000,DF400] The end result of this example link is a disk file called MYPROG.COM which will execute at C000h and has data areas at F400h and up. This program should also run in ROM if it has the correct I/O drivers. The "S" switch causes the library to be search as to keep the result size to a minimum. 7.3. NON CP/M I/O ================= As supplied, ASMLIB uses CP/M for its I/O. This can be annoying if ASMLIB based software is to be run outside of CP/M. This can be partially overcome by using the USERIO routine to patch in the address of custom I/O drivers. The complete fix for different I/O environments is to replace the standard ASM LIB drivers with custom ones. ASMLIB uses only console input, output, status and printer output and status routines so a new set of I/O drivers is pretty easy to make. Supplied on disk are drivers (SBC800IO.ASM and .REL) for the SBC-800 cpu card from SME Systems. Linking the SBC800IO.REL or a similar modified I/O driver allows ASMLIB to be run in any I/O environment. This file may be modified to suit the hardware in use. When modifying an I/O driver module it is highly recommended that the IONUM function is modified to reflect the new driver. When this is done, application programs are able to test if the correct driver module has been linked to them. 7.3.1. Linking I/O drivers ----------------------------- To replace the I/O drivers module requires a little more work than most other modifications. This is because the I/O modules are differently named and there fore the substitution cannot be submit driven quite so easily as was done be fore. Before starting, make sure that you have a backup original copy of ASMLIB.REL safely tacked away. If a mistake is made in the command lines, ASMLIB can easily be erased. To change the I/O drivers, you must know the name of the I/O driver module cur rently in the ASMLIB. As supplied the name is CPMIO (source supplied on disk). Assuming that your drivers are called MYIO the following command will replace ASMLIB's CPMIO module with your I/O drivers. A>LIB ASMLIB=ASMLIB From this command it is obvious that the name of the I/O drivers in the ASMLIB must be known. 7.4. Space vs Size ================== For ROM based routines it is probable that space is of the essence so the reader may be dubious of ASMLIB since it does use a little more than the optimized coded software. But think awhile. At worst ASMLIB can probably be used to get the program running under CP/M then modified in a few minutes to run in the ROMs. This is nice. It allows you the ability to simulate your program and to use debuggers etc on it. When your prog ram has been got running you can replace ASMLIB routines with your own which may be better or smaller or faster, depending how clever you are. In any case, you have a program running which can be modified or evaluated. If you have got some money then you can purchase the source code to ASMLIB and fiddle with it till it does only your job and no more so as to optimize it. At best, ASMLIB can be used and kept intact in the ROM based situation. Consider very carefully before abandoning ASMLIB that the cost of average EPROM is pro bably one fifth to one tenth of the cost of a single hours contract labour! For one-off applications or small runs, ASMLIB is therefore well worth the expense in size. Its routines are not that stupid, effort has been expended in their writing so that they are efficient. Consider also that memory devices are constantly expanding in capacity so that super-tight code may less of a problem unless you have a large program. It is also worth remembering that as smaller memory chips are superceeded they become more expensive than larger capacity devices. This is because of smaller demands, smaller quantity purchases and smaller production runs. Small is not necessarily better. 8. I/O Routines and Modifications *********************************** The distribution disk supplied quite a few customize I/O driver modules for ASMLIB. As mentioned earlier, they can be immediately used to change the type of device that the library sends its I/O to and from. The following sections go into details on some of the files and also describe the use of the I/O attribute and function patcher program called SETUP.COM which is also supplied on the disk. 8.1. The special I/O drivers ============================ All of the special I/O driver module files supplied have been written with a purpose in mind and are supplied as an aid for the user to modify for a special application or to use as standard. The most important thing to remember is that the standard entry points in the library module is still kept intact. This is vital. It is strongly recommended that any new replacement modules that are generated for ASMLIB keep the same entry point names as the modules they are replacing. This allows the linker to integrate your replaced code into existing application programs without any editing or reassembly. All the I/O drivers implement X-on/X-off handshaking when an equate is set to true. If set false then no handshaking is done. A problem can easily arise when using XON/XOFF handshaking. This occurs when keyboard characters can be "gobbled" in the search of handshaking characters so that programs that do console status checking while outputting data can lose any incoming characters. This is overcome by using the I/O drivers without the hand shaking (set equate to false), reassemble and re-link). All modules are supplied with handshaking switched off. It is up to the user to provide any handshaking required. The supplied I/O modules are described below. 8.1.1. CPMIO --------------- This is a standard (default) I/O driver module for ASMLIB. All I/O is done via CP/M and is done using the direct I/O function which bypasses all of CP/M's checking processes. This module has built-in XON/XOFF handshaking enabled via an equate which is normally supplied switched off. The LOE function in ASMLIB is directed via the standard CP/M list device and the LST (list status) function is disabled. 8.1.2. SBC800IO ------------------ This is a general purpose I/O driver module based around the SBC-800 single board computer card from SME Systems. This module directly communicates with the hardware on the board making it suitable for ROM based applications with the SBC-800. A serial terminal is implemented via the first serial channel and the printer can be either a parallel printer or a serial one. In the source code file there is a number of equates that can be set for tailo ring the module. The first is the XON equate, when true causes code for handsha king to be put into the program. The SERIAL equate when set to TRUE causes the LOE function in ASMLIB to send their output to the serial printer port on the SBC-800. No initialization of the serial printer port is done by ASMLIB so that the baud rate of the serial prin ter will be left as is. If the equate is set to FALSE then all, output will be directed via the parallel centronic type printer interface port on SBC-800. 8.1.3. CPMMPC ---------------- This is a particularly specialized module that uses the six channel buffered serial I/O card called MPC-6 as well as CP/M for its console I/O. The unique feature of this module is that it intercepts special codes from the user's program and uses them to switch or re-direct I/O so that the console can be switched to a different I/O device. This means that the user can send a code that causes the console to become one of the serial channels on the MPC-6 or a CP/M device for a total of seven destinations. The codes that CPMMPC intercepts and the action taken are as follows. Table 8-1: I/O Re-Direction Function Codes -------------------------------------------------------------------- Code Device selected -------------------------------------------------------------------- F8 Standard CP/M I/O routines, this is the default mode F9 Select MPC-6 channel 0 for all subsequent I/O FA 1 FB 2 FC 3 FD 4 FE 5 When the above codes are sent to the COE function, they are intercepted and cause the above described action to take place. When a channel is selected it stays selected till it is changed with another one of above special codes or until the program terminates to the operating system. The action of sending a code to this special I/O driver module and re-directing I/O is (here) called LOGGING ON since this is action that is taken, the user logs onto a new device. The example that follows shows this login on in action. ; ; This simple functional piece of code reads all the serial channels and echoes ; them back to the terminal that entered them. If a control C is entered then ; it causes a quit to the operating system. A "?" causes a little message to be ; displayed on the current terminal. This is a simple test program. ;base equ 0F8h ; base code for login on ; start: xra a ; load A with a 0 continue: sta channel ; save as a channel selector adi base ; add the log on offset call coe ; do the log on ; we are now logged onto a channel of MPC-6 or CP/M at base + channel call cst ; is there a character there? cpi 03 ; control C? jz exit cpi '?' ; does user want a little help? jz help call coe ; echo the character ; ; The following code just increments the channel selector byte and tests if we ; go back to channel 0 ; ignore1: ; go to a new channel lda channel inr a cpi 7 ; illegal value jz start ; go to channel 0 if all checked jmp continue ; use this channel then ; ; Re-select channel 0 then exit to the operating system ; exit: mvi a,base call coe ; select channel 0 jmp quit ; exit ; help: call inline db 0Dh,0Ah,'MPC-6 and CP/M Test Program.' db 0Dh,0Ah,'Console characters are echoed and' db 0Dh,0Ah,'a control C exits to CP/M$' jmp ignore1 ; go to the next channel ; The above code illustrates how easy it is to generate a working program that reads all the seven consoles, does elementary character checking and a little I/O. This is the power of ASMLIB and integrated library of functions. 8.1.4. SBCMPC ---------------- This I/O driver module is very similar to the CPMMPC with the only difference being that the SBC-800 is used for standard console I/O with the MPC-6 remaining as the extra log-on channels. This has been included for ROM based dedicated applications. In all respects other than the fact that SBC-800 is used instead of the default CP/M I/O driver module, this module is used in exactly the same manner as the CPMMPC module. 8.2. Different terminal support=============================== The source code for the attributes and smart screen function modules has been included on the distribution disk. The user is encouraged to do patches as re quired so that the terminal in use is fully supported. All terminal dependent code in these files have been put into tables so that only the most simple of editing is required. After the editing, the files can be assembled and re-linked into ASMLIB to support your terminal. The SCREEN.ASM file on the distribution disk contains all the cursor based com mands as well as simple screen commands. This file has only one table at its end which is set out as follows. Table 8-2: SCREEN.ASM Data Table ----------------------------------------------------------- Byte Label Usage ----------------------------------------------------------- 00..03 id Used by SETUP to locate this table 04 xyfn Cursor addressing string length 05..09 Cursor addressing lead in string 0A rowcol Set to 0 means send row first 0B xoff Screen column address offset 0C yoff Screen row address offset 0D..0E filler Not used, makes up space 0F..13 cpfn Erase whole page string 14..18 clfn Erase to the end of line 19..1D epfn Erase to the end of page 1E..22 ecfn Enable cursor function codes 23..27 dcfn Disable cursor function codes ----------------------------------------------------------- In the codes that are used in SCREEN.ASM and in SETATT.ASM, all string of codes that are sent to a terminal to cause a function have a leading string size byte. If this byte is set to 00 then no action is performed by the routines in ASMLIB. The SETATT modules has TWO tables in it. The first is a function code SET table, and the second is the function CLEAR table. Just before the first table is the string of six bytes whose address is returned by the TNAME function. This string of bytes should be set to indicate the type of terminal that has been selected. Table 8-3: SETATT.ASM Data Table ------------------------------------------------------------- Byte Label Usage ------------------------------------------------------------- 00..03 setid Used to find this table by SETUP.COM 04..09 termnam Six byte terminal descriptive name 0A..0E attset Set half intensity string 0F..13 Set blinking characters 14..18 Set reverse video 19..1D Set underline 1E..27 Reserved (future expansion) 28..2B Used to find this table by SETUP.COM 2C..30 Clear half intensity 31..35 Clear blinking characters 36..3A Clear reverse video 3B..3F Clear underline 40..49 Reserved (future expansion) ------------------------------------------------------------- The second method of program modification or tailoring involves using a utility program to patch end result programs so that their tables of terminal codes are correct. This is done with SETUP.COM program. This program and others on the distribution disk are described next. 9. ASMLIB Utilities ********************* On the distribution disk there are utilities and test programs that have been written using ASMLIB. These are supplied as an example programs and some of them are useful stand alone programs that may be of use. These utilities and test programs are described next. 9.1. SETUP.COM - An initialization program ========================================== When an application program is linked to ASMLIB, the user can patch all terminal dependent codes to suit a new terminal. This patching process uses a program called SETUP and is fully menu driven so that minimal knowledge of the terminal, its handbook and these few lines of help are all that is required. SETUP is initiated with the command A>SETUP No filename may be specified on the command line. The file TERM.DAT contains all terminal definitions and should also be on the default disk. If it is not then a small menu will appear which will ask if a quit to the operating system, log onto another disk or generation of a new TERM.DAT file is required. Since all code for patching must come from TERM.DAT then the file must be accessible to the program. The user can try to read the TERM.DAT on another drive. The SETUP after finding the TERM.DAT will display a signon message and then will show a menu of possible actions. The user should now press a key corresponding to a required action as displayed on the menu. When a key is pressed, the action will be immediately initiated, no carriage return is required. The following is a list of the actions that are allowed with a little description of each. When entering data into this program to define terminal attributes and function code strings, the program has been set to accept keyboard entries directly into memory so that, for example, to enter a clear sequence for a Televideo terminal requires only pressing an Escape and "*" keys. The program does not use hex or decimal representation of keys. Up to four keys may be pressed and when four keys are pressed, the program asks for confirmation. The Enter key may be pres sed to end the the data entry sequence if less than four bytes are required in the sequence. Do not press the key without checking the screen display first. 9.1.1. Edit Terminal Definition ---------------------------------- The terminal editing section allows the user to display definitions that are stored in the TERM.DAT and optionally to edit them. Very useful for correcting erroneous definitions. The program asks for a terminal definition number that is to be edited. The user can respond with 00 (or a Return) which will terminate this routine, or a number which represents a definition number. The definition number must not be greater than the total number of definitions on file else an error message will be dis played followed by another prompting message. When a number is input, the screen will show all the codes that have been set in the data file for this terminal. The program will ask if this definition is to be edited and if not then it will return and ask for a new definition. If this definition is to be edited then the program will sequentially ask if a part of the definition is to be changed and when "Y" is entered then the user may enter new data. When all sections in the definitions have been displayed or edited then this definition may be written back to the TERM.DAT after the confirmation. If no confirmation is given then no action will be taken and the edited definition will be lost. 9.1.2. Kill a definition --------------------------- This section of the program allows the user to delete a definition from the TERM.DAT file. The program reads all the definitions into memory then asks the user for the ones to be deleted. Each definition that is to be deleted has its descriptive string displayed then after confirmation that it is to be deleted, the defini tion is deleted. After the deletions are done, memory is written back to the TERM.DAT leaving all the definitions marked for deletion behind. This is done after a confirmation. 9.1.3. Replicate a Definition -------------------------------- This section of the program allows the user to copy a definition in the TERM.DAT file and place the copy at the end of the file (to append it). This allows the user to quickly generate definitions for similar terminals which only require a little editing to make them correct. The program asks for a terminal definition to be copied then it reads the defi nition into memory the appends it at the end of the TERM.DAT file. 9.1.4. Display Terminals on file ----------------------------------- This section prints all the descriptive strings of all the terminal definitions in the TERM.DAT file. This is handy for knowing what terminal are supported (so far). The output from this section can be sent to the printer. 9.1.5. Create a terminal definition -------------------------------------- This allows the operator to create a new terminal definition and place it at the end of the TERM.DAT file. The user is asked all about the terminal and how it is used in various modes. All the data are stored in memory then at the end of data input they are written after a confirmation to the TERM.DAT. The user has a number of options which can be used to re-enter the data if a mistake has been made. These are fully menu driven as the program progresses. 9.1.6. Install a .COM file ----------------------------- The tables of terminal dependent codes in all .COM files generated by linking to ASMLIB have four bytes header which identifies them. This section of SETUP scans through .COM file on the disk and finds the table headers then writes new termi nal data into the program's tables. This section is the reason for existence of the SETUP.COM. The program asks the user for a file to be patched. The user must enter the name only (not the .COM extension) with optional drive specifier. The program then opens the file and searches for the table headers. As each table is found, the currently selected terminal definition is written into the table and a little message is shown on the screen to indicate this. At the start of this section, the current terminal is shown and the user may change this to any terminal in the TERM.DAT. If no terminal has been selected then the program will ask for one. 9.1.7. Quit to CP/M ---------------------- This option closes the TERM.DAT file and returns to the operating system. As an aid to the user, the small text file TERM.OVL on the disk may be printed out and used as a guide for the keys that must be pressed to install a terminal. The terminal reference manual can be read and the required codes written direc tly on the TERM.OVL printout. After this, the CREATE section can be called and the definition can be entered in a few seconds. 9.2. FP.COM - A file display and patch program ---------------------------------------------- One of the more common things that is done by programmers and hobbyist with the computer is the display and modification of binary data (or command) files. Usually this is done with programs like DDT or SID that reads the file into memory then command allowing basic modifications and display are used to modify the data before writing them back to the disk. This is not really a good method of doing the modifications. FP.COM was written to allow far more powerful and flexible display and modifica tion of binary files. FP is a fully screen oriented sector editor that has many powerful and flexible commands. The user is able to position the cursor over displayed bytes in either hex or ascii and to optionally enter data into the sector. The user has a number of file options that allow opening of files, read ing sectors, writing sectors, shuffling, etc. FP is initiated with or without a file name which will be used for subsequent operations. If no file name is given then the program will ask for one after displaying the signon message. When the file is opened, its size is determined then the first sector is dis played on the screen. Also on the screen will be the editing menu which shows a list of all the options that are available to the user for entry of data. When the sector is displayed on the screen, the user can use some of the com mands shown in the menu to do the actions described, or the user can enter data directly in hex or ascii mode. The user is also able to enter the file section of the FP so that sectors can be read, written, displayed, or seek to. This allows the user to perform operations on a sector size basis. One powerful feature of FP is its ability to append a sector to the end of a file thereby increasing its size. This can also be done on a repeated basis so as to quickly generate a file of many sectors from a single (edited) sector. This is all menu driven so that no problems should be encountered in its use. The command of FP are meant to be self explanatory. The on-screen menus and the immediate actions on key-presses helps to make this a particularly fast and powerful program. The menus provide a lot of user guidance and the commands are simple to use and understand. The user is encouraged to spend a little time in getting used to them. A program like this also tends to make applications for itself once the user understands it so that previously impossible things become easily coped with. The use of FP will hopefully be clear now. It is intended to be used as a power ful and flexible means of displaying and patching files. This program uses ASMLIB for all its I/O and most of the message processing and numerics. It is strongly recommended that this program is not modified as a prompting message will appear indicating that the program is corrupted. The same action will occur in SETUP.COM if any modifications to it are done, so be warned. 9.3. TESTALL.COM - A test program ================================= TESTALL is a program that was originally written to allow a technician to test some of the boards manufactured by SME Systems. As the request for more tests arose, the program rapidly grew in size so that at present it is quite large, though very segmented and straightforward. The tests enabled are those that usually require some fiddling, such as checking all the parallel ports on an I/O card (all 72 bits) or checking the channels of an MPC-6 serial I/O card. The memory test section of TESTALL also has some high ly sophisticated routines in it which test all software combinations that have previously caused dynamic memory boards to "fall over". One other consideration also is that the hardware that the program will be run on is sometimes a little "shaky". To this end it is quite reasonable to assume that the program will at some time become corrupted and fail. To overcome this problem, the program check itself using a CRC and if mismatch occurs than the error message is flashed on the screen. This is enough for the user to under stand that a problem exists and that a new copy of the program is required. This program is also a very good example of the type of program that can be written with ASMLIB in a short time. The program has a very large amount of simple processing that displays messages and uses some of the more exotic rou tines from ASMLIB such as the CRC and random number routines. The inclusion of ASMLIB routines has also made this program very easily maintained and should be able to adapt to changing needs. The functions of TESTALL are being expanded at a rate of about one per week so no attempt will be made to explain them other then to say that the program is menu driven and that instructions are given at every step of the program. This program has been designed to be run without any instructions. Try it and see! 9.4. HLP - A HELP Screen handler ================================ HLP is a very powerful facility that allows a program to quickly and easily display pages of text files on the screen from an application program. The programmer is able to write the conventional application program and at a later date easily incorporate facilities that HLP provides. This means that existing programs can be enhanced so that the user of the program can summon pages of text on the screen in an interactive manner. This is commonly called HELP facility. HLP is supplied in both source and .REL forms on the distribution disk so it can be used as is or it can be modified to suit the application. Modifications are encouraged. 9.4.1. Using HLP ------------------- The programmer is able to incorporate HLP into an existing program in a very small time because HLP has been designed to be interfaced to in a very straight forward manner. The following steps show the recommended procedure for doing this to a completed running existing program. 1) Edit the file to include the required code (see later). a) Initialization code. b) Screen selection code. c) Error handlers. 2) Re-assemble the file. 3) Link to HLP.REL as well as any other files. 4) Create the required text files for display. After this is done the program can be run and the help facility used. The fol lowing sections go into detail about the three programming steps required. 9.4.2. Help File Format -------------------------- The files the HLP indexes into for the programmer are meant to be laid out in a simple straightforward manner. The file is split up into sections. Each section begins with a double ":" cha racter. A section continues till another double ":" is encountered. At initialization time, the whole file is scanned and the double ":" characters are looked for. The addresses of these characters are saved so that later dis play commands to display a section can quickly access the text in the file. 9.4.3. HLP - Initialization Code ----------------------------------- The initialization of HLP requires the user to pass the address of a proper random access file control block (a FCB). This FCB will be used by HLP to access the text file and must not be interfered by the user or programmer. An example of passing the FCB address to the HLP follows. start: lxi d,fcb1 ; point to file number 1 call inithlp ; initialize the hlp program ; ; Continue on from here ; fcb1: db 00,'TESTALL HLP' ; drive, file name, file type db 0,0,0,0,0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0 During the initialization section in HLP, the whole of the file is read and all the KEYs are extracted and saved in a table. The key is a sector number and the sector offset that the double ":" occurred at within the file. This searching can take a few seconds with large files so a little planning of where to place the initialization code may be required. It should also for the same reason only be called once in the program for a given file, but it may be called may times if different help files are to be displayed. 9.4.4. HLP - Screen selection Code ------------------------------------- To select a screen from a text file, the programmer must passed the NUMBER of the screen that is to be displayed to the HLP entry point. This number is then used by HLP to get a key from the table that is generated on initialization then the text file is read (read random) and the text screen is displayed. This is very simple. An example follows. start: mvi a,5 ; help screen 5 call disphlp ; display it ; ; continue on from here From the above it is obvious that only 255 screens of a file can be displayed. This is not a real problem as this can represent a very large text file. Also this number is used to access the text file by SECTION and not by page so that a section can be many pages long, not just a single page. A vital consideration in using HLP is the error return from the initialization and the display entry points. These must be trapped and handled. 9.4.5. HLP - Error Handlers ------------------------------ On return from the initialization and display sections, the accumulator will have a value in to indicate the status of the program. If a ZERO then no problem was observed and we may continue on normally. This is all shown below. It is the programmers responsibility to handle the error codes. Table 9-1: HLP Error Codes -------------------------------------------------------------------- Code Cause / Reason -------------------------------------------------------------------- 0 All OK, proceed normally, zero flag set. 1 File not found. Bad error. This is usually caused by a missing text file or incorrect FCB 2 No section start marker found. This error means that no keys were found and that no screen can be displayed. 3 Too many section start markers found. Only 255 section start markers are allowed in a file. All marker past 255 are ignored, previous markers work normally. 4 Read error. Caused by a modified FCB. This is caused by bad sectors or by an FCB that has been modified by the application program. 5 Program error. Unknown cause. Re-initialize. A diagnostic message is printed. Try re-initialization. 6..255 Undefined error. Program error if returned. -------------------------------------------------------------------- The above errors are quite self explanatory and are very reasonable to trap. At no stage should the program go "crazy" or perform any harmful actions. It will calmly and quietly return an error message saying that is having a hard time. 10. The Distribution Disk ************************** On the distribution disk there are quite a few files in addition to ASMLIB. Some of these are very briefly described below. This manual describes a number of them. README .1 - Small text file with comments ASMLIB .REL - The library CPMIO .ASM - Source to the standard CP/M I/O drivers SBC800IO.ASM - Source to I/O drivers for SBC-800 CPU card ATOD .ASM - Analog to Digital card interface module SCREEN .ASM - Source for the screen dependent terminals SETATT .ASM - Source for the screen dependent terminals PROLOG .ASM - Source for the prolog module CLOCK .ASM - Source for time functions, hardware dependent TASM .ASM - Source for a demo program RN .ASM - Source for random number test program HLP .ASM - Source for an inline help facility LIBASM .SUB - Submit file to generate a new library from the sources MAKEASM .SUB - Submit file to assemble whole source code disk MODLIB .SUB - Submit file to change a library module ADDLIB .SUB - Submit file to add a library module FP .COM - File dump and modification utility SETUP .COM - Program file patcher and installer TERM .DAT - Terminal definitions file, used by SETUP.COM TERM .OVL - Terminal code input form, used by SETUP.COM 11. Monitor Command Summary **************************** Command A a Display memory as ascii at location a B n Select memory bank n D a Display memory at location a (hex) for 80h D a b Display memory from a to b (in hex) E a Examine memory at a and optionally alterF a b c Fill memory from a to b with byte c (in hex) G a Go to a program at a and execute it H a b Display the hex sum and difference b/n a and b I pp nn Input and display from port aa for nn times K Display current time K Y M D W H M S Load the real time clock L a b s Locate string (up to 5 bytes) in memory from a to b M a b c Move memory b/n a and b to location string at c O pp dd nn Send dd to port pp nn times P pp Display and optionally alter port pp P ^ Display all ports in the system P & Continuous port display, cursor positioned Q Quit the monitor T a b Simple test of memory from a to b V a b c Verify if memory from a to b is the same starting at c X Display registers M> Prompting sign Esc Ceases any command or routine 12. References *************** In the design of ASMLIB the market was surveyed and lots of reading on assembly language programming done. The following references are a cross section of the best sources of code, information and programming practice that the author has read. Books: ----- "Z80 Assembly Language Subroutines" by L. A. Laventhal and W. Saville, Osbourne/McGraw-Hill "A Programmers Notebook" by D. E. Cortesi, Prentice-Hall, 1983 "Structured Programming Using PL/I" by J. Hume and R. Holt, Prentice-Hall, 1982 "More TRS-80 Assembly Language Programming" by Bill Burden, Radio Shack "8080 and 8085 Software Design Book 2" by C. Titus, D. Larsen and J. Titus, H. W. Sams & Co. "Software Tools" by Kernigham and Plauger, 1976 Software: -------- SYSLIB Available through the CP/M or SIG/M users groups is also a group of assembly language routines. Ideas only were taken from this source since it is meant to run under a tricky operating system and uses the Microsoft assembler instead of Digital Research's RMAC. CP/M UG Volumes 1 to 90 of the CP/M user group disks have been closely looked at and software from here includes the random number generator. Since this product is being sold at a nominal price there is no ethical question arising from this source. BB UG The Big Board user group software disks and listings. Periodicals "Byte", "Lifelines", "Microsystems", "Engineering" all provided input of ideas for ASMLIB.