Soul of CP-M
Soul of CP-M
Soul of CP-M
by
F IR S T E D IT IO N
T H IR D P R IN T IN G - 1986
If the answer to any of these questions is yes, then this book, with its
unique approach to teaching both C P /M systems calls and 8080 assembly
language programming, is for you. Starting with simple three- and four-line
programs, we ease you into the “ soul” of C P/M : the universal system calls
that make C P /M the world’s most popular microcomputer operating system.
Gradually and easily, you’ll learn how to write programs to control all your
I/O devices, including the disk system, and, also, perform a variety of other
functions.
Remember, you don’t need to know how to program in assembly language
to understand this book! We’ll teach you all the 8080 assembly language that
you will need to know and, since you’ll be learning the system calls at the
same time, your programs will be able to perform powerful functions from
the very beginning.
You’ll also learn how to use these powerful system calls in your BASIC
programs, how C P /M manages disk files, and how to modify C P /M ’s
“ BIOS” (Basic In p u t/O utput System) to work with different I/O devices, so
that you can customize C P /M for a particular printer or other device.
All in all, if you want to do more with your C P /M system than simply run
applications programs, then this book is for you!
M it c h e l l W a it e
and R o ber t La f o r e
Acknowledgments
The authors would like to thank Mark Berger, Phil Chapnick, and Scott
Kamins for their many helpful comments, criticisms, and suggestions. Their
pioneering journey on the road followed by this book has resulted in remov
ing many obstacles from the path of future readers.
CHAPTER 1
T h e B ig P i c t u r e : H ow C P /M I s O r g a n iz e d ................................................... 17
W hat Is an Operating System, Anyway?—W hat’s So G reat About
C P /M ?-T h e Parts of C P /M —8080 Architecture—D D T-The
Programmer’s X Ray and Probe—Back Down to Earth
CHAPTER 2
ONE T o e IN t h e W a t e r : Console System C a lls ....................................... 31
Console O utput System Call—Get Console Status—Barber-Pole
Display Program -Console Input—Executing Programs From
C P /M —System Rest-A Warm Boot—So Long, Chapter 2, It’s Been
Good To Know You
CHAPTER 3
G e t t in g in D e e p e r : Advanced Console System Calls .......................... 71
Print S tring-R ead Console Buffer—Echo Program -N am e Display
Program—Direct Console I /O -L is t O utput to P rinter-R eader
In p u t-P u n ch O u tp u t-G et I/O B yte-Set I/O B yte-Goodbye,
Nondisk System Calls
CHAPTER 4
U s i n g t h e A s s e m b l e r ................................................................................................... 107
W hat’s an Assembler Do, Anyway?—W hat ASM Does—The
“DECIBIN ” R outine-Reads Decimal From K eyboard-D E C IH E X
Program-Converts Decimal to Hex, on Screen—BIN IHEX-Binary
to Decimal Conversion Routine—Using C P /M ’s Submit U tility—
G raduation Time
CHAPTER 5
D is k S y s t e m C a l l s ............................................................................................................137
Records, Files, Tracks, Sectors, Allocation Units, Extents, and
Goodness Knows W hat Else—Talking to BDOS—Open File—The
Problem W ith Where the DM A Is Located—Read Sequential Sys
tem Call—Set DM A Address—TYPE2 Program -Im itates the
“TYPE” Comm and—LINES Program -Prints N um ber of Lines in
Text File—Life on the Fast Track
CHAPTER 6
W r i t i n g t o t h e D i s k ......................................................................................................169
Writing a Sequential Record—Make File—Write Sequential Rec
o rd-C lose File System Call—Program to Write a Sequential Rec
ord—STORE Program-Stores Text in F ile-D elete File System
Call—Random R ecords-R ead Random System C all-W rite R an
dom System Call—RA ND Y M O D -Program to Modify a Random
Record—Compute File Size System C all-S et Random Record Sys
tem C all-O u t of Ink
CHAPTER 7
S o u l S e a r c h i n g : Wildcards and the Disk D ir e c to r y ...............................201
How C P /M Stores Files on the Disk—Search For First System
Call—W ildcards—Search For Next System Call—Erased Files—Sav
ing an Erased File—The Bit M ap-W O R D S Program-Counts
Words in Files and Uses Wildcards
CHAPTER 8
T e a m w o r k : Using System Calls From B A S IC ............................................243
Where Do We Put the A-L Program in Memory?—How To G et the
A-L Program Where We W ant It To G o—How Do We Transfer
Control Between BASIC and the A-L R outine?-H ow Do We Pass
Arguments Between BASIC and the A-L R outine?-B IN IH E X 2-
A-L Routine Called From B A SIC -O ther Ways To Put the A-L
Routine Into M em ory-H EX IB IN 2-Passing Arguments to BASIC
From an A-L R outine-O perating on Strings With an A-L Rou
tine-B ack to Basics
CHAPTER 9
T h e I n n e r m o s t S o u l o f C P/M : H ow To Modify C P /M for
Different P eripherals............................................................................................ 279
Why You’re Reading This C hap ter-W h at Is the BIOS Anyway?—
Learning Your Way Around the B IO S-T he Complete BIOS List
ing—How to Modify Your Printer Driver—Installing the New
Driver Into Your B lO S-Inserting the New Driver Into the C P /M
System -A Shortcut-M odifying BIOS for Different Control Char-
acters-T he Sky’s the Limit
A PPENDIX A
H e x a d e c im a l N o t a t i o n ............................................................................................... 323
Why Use Hexadecimal N otation?—Binary N otation—Decimal
N otation—Hexadecimal N otation—Converting Hex to D ecim al-
Converting Decimal to Hex
A PPENDIX B
U t il it y P r o g r a m s ............................................................................................. 331
H E X D U M P-M icro Space In v ad ers-H E X ID E C -F IL E D U M P
APPENDIX C
S u m m a r y o f 8080 I n s t r u c t i o n s ................................................................... 345
8080 Architecture—8080 Instructions—Assembler Directives
APPENDIX D
T a b l e s ..................................: ............................................................................. 359
ASCII Character Set With Hexadecimal Equivalents—Hexadeci-
mal-to-Decimal Conversion—Multiples of IK (1024), in Decimal
and Hexadecimal—Decimal, Hex, and Binary Conversion
APPENDIX E
S u m m a r y o f B D O S S y s t e m C a l l s (F o r C P /M 2.2) 365
A PPEND IX F
S u m m a r y o f D D T C o m m a n d s ......................................................................369
Loading D D T —“A” for Assemble—“ D ” for Dump M emory—“ F ”
for Fill—“G ” for G O —“ H ” for Hexadecimal Arithmetic—“ I” Com-
m a n d -“ L” for L is t- “M” for M o v e -“ R ” for R e a d - “ S” for Set
M em o ry -“T” for T ra c e -“U ” for U n tra c e -“X” for Examine Reg
isters
APPEND IX G
S u m m a r ie s o f P r o g r a m s U s e d a n d L o c a t io n s o f I n s t r u c t io n
D escription s ....................................................................................................................... 379
Programs U sed-D escriptions of Instructions
In d e x 383
Introduction
SOUL OF CP/M®
11
Soul of C P /M ®
in the C P /M environment. In fact, this efficient facade is all that many users
will ever know about C P/M . And yet, below this smooth and easy-going sur
face, C P /M has a whole different level; a powerful inner structure that is
easily used if you know how, which can control your com puter’s input and
output devices, including the disk drives, with a precision and a versatility
that is impossible to obtain from a higher-level language. We call this deeper
and more powerful level the “ Soul” of C P /M and, in this book, you will learn
all about it.
First, this book teaches how to use C P /M ’s built-in system calls. These
system calls are the key to programming in a C P /M system, since they allow
your program to communicate with a wide variety of I/O devices, using a
universal format that works on any C P /M system. Once you’ve learned how
to use these calls, you are freed of the restraints imposed by BASIC or what
ever other high-level language you are using. You can directly access the
video screen, the keyboard, the disk system, and other I/O devices, so that
they respond the way you want them to, not the way the designers of your
particular language decided they should.
Second, you will learn all about the C P /M disk system. You will learn how
it is organized, and how you can take control of it for use in your own pro
grams.
Third, you will learn how to “customize” C P /M to work with different I/O
devices. Since there is no universally accepted format for the communication
12
Introduction
A “chip” is the tiny slice of silicon which contains the thousands of transis
tors that make up a microprocessor. The exact design of this chip determines
the “instruction set” of the computer; that is, it determines what commands
you have to give it to make it work. Different chips are given different names.
One of the most famous is the “ 8080” chip manufactured by Intel Corpora
tion. After this chip had been in production for a time, Intel improved it by
coming out with a faster version called the 8080A. Later, Intel added the
8085 chip, which is very similar to the 8080 and 8080A, except for some
improvements in the way that it handles interrupts.
Throughout this book, when we refer to the “8080” microprocessor chip,
we are also referring to the 8080A and the 8085. The differences are relatively
minor and, in any case, only apply to the interrupt system which we will not
be concerned with.
Also, this family of chips is “upward compatible.” This means that any
program written for an earlier chip (the 8080, say) will run on any later chips
(the 8080A and the 8085). Thus, even if we used the interrupt system, by
programming for the 8080, we ensure that our programs will run not only on
the 8080 but, also, on the 8080A and the 8085 as well.
The Z-80 is a chip manufactured by Zilog. Although it is also upward
compatible with the 8080, it has a considerably enlarged instruction set.
Generally speaking, programs written for the 8080 will run on the Z-80 as
well, although there are exceptions.
What it comes down to is this. The programs that you learn to write for the
8080 microprocessor will also run on the 8080A, the 8085, and (usually) on
the Z-80. So, no m atter which of these chips is used in your C P /M system,
this book will tell you what you need to know to write working programs.
13
Soul of C P /M ®
Chapter 3 will advance further into the realm of system calls and you’ll
learn how to handle strings of text, both for input from the keyboard and
output to the screen. Throughout Chapters 2 and 3, you’ll also be learning
the rudiments of assembly language, using D D T as a fast and easy way to try
out the small programs needed for the examples.
In Chapter 4, we’ll introduce you to ASM, the C P /M assembler, which
simplifies the writing of larger assembly language programs. You’ll also write
and operate a useful program —one which translates hexadecimal numbers
(the kind the computer uses) to decimal numbers (the kind humans use) and
makes use of the system calls you’ve been learning. (In case you’re not famil
iar with the hexadecimal numbering system, it’s described in detail in Appen
dix A.)
Chapters 5 and 6 cover the disk system. You’ll learn about the fundamen
tal building blocks of disk storage: records and files, and how to manage
them. You’ll also write a variety of programs making use of the disk system
calls. These programs will be used to write files and retrieve them from the
disk in both sequential and random format, and there will even be a program
to count the number of lines and pages in a file. As a bonus, we’ll delve into
the mysterious world of C P /M file directories and you’ll learn how to “res
cue” a file which has been mistakenly erased!
C hapter 7 covers a larger program in detail. This is “W ORDS”, which
counts the number of words in a text file. This program will make use of
“wildcards” (the use of * and ? in a program name to represent unknown
characters). It will also introduce the idea of “stack management,” so that
you can avoid a variety of pitfalls in your programming.
Chapter 8 deals with how to use system calls and assembly language from
BASIC. You’ll learn how to “call” assembly language routines from BASIC,
how to pass numbers back and forth between BASIC and your assembly
language routine, and where to put all these routines in memory. As exam
ples, we’ll use routines that allow you to use hexadecimal numbers in BASIC
and allow you to convert BASIC string variables from lowercase to uppercase
letters. Although BASIC is used as the example language here, many of the
techniques described are applicable to other high-level languages as well.
In Chapter 9, we explain how to go about modifying your C P /M to use
different I/O devices. A specific example—writing a driver for a particular
printer—will be described in detail.
Finally, a number of appendices are given that cover hexadecimal notation
and provide summaries of all C P /M system calls, 8080 instructions, and
D D T commands. They also include some useful and entertaining programs
which make use of the material covered in the book.
15
Soul of C P /M ®
16
CHAPTER 1
Soul of C P /M ®
chapter. And, finally, we’ll briefly discuss DDT, a program that lets you write
short assembly language programs quickly and easily.
The idea of this chapter is to provide you with a sort of aerial view of the
terrain we’re going to cover. D on’t worry about specific details yet. W hat
we’re interested in here is concepts: what an operating system is, how it does
what it does, how the computer itself operates, and why we need a program
like DDT. In the next chapter, we’ll get down to specific examples and, then,
the broad outlines described in this chapter will become clearer.
Program Transportability
System Calls
but they can also be used from BASIC or other higher-level language p ro
grams if you know how. (We’ll cover the use of system calls from BASIC
in C hapter 8.)
Machine Transportability
Interchangeable
with many
different ■Interchangeable with many
computers. I/O Devices different brands of
(tape, keyboard, etc.) I/O devices.
20
The Big Picture
C P /M ’s Golden Rule
Top of memory / /
FFFF hex
BIOS
/ F200 hex FDOS
BDOS
/ E400 hex
CCP
/ DC00 hex
llllllllllllll
TPA User’s
program
/ 0100 hex
Zero page
/
0000 hex
21
So l o f C P /M 5
thing about the hexadecimal numbering system, now is the time to become
familiar with it by reading Appendix A.) The size of the TPA is dependent on
how much memory your machine has. In a 64K system (the maximum for
most systems running C P/M ), the TPA will be about 56,000 (decimal) bytes
long. (It will probably be a day or two until you are writing programs that
large!)
The next part of the C P /M system is called the CCP, for Console Com
mand Processor. This does just what the name says. It deals with commands
typed in by the user from the console keyboard. Thus, every time you see the
“A > ” prompt, it is the CCP that printed it and the CCP is waiting for you to
type something in on the keyboard. When you do type something in, the CCP
will either deal with your command itself, if it is a “resident com mand” like
D IR or TYPE, or it will call another program if it is a “transient command”
like STAT or PIP. The CCP might start at around DC00 hex, in a 64K sys
tem, and will occupy about 2000 (decimal) bytes.
Although the CCP must listen to your commands that are typed at the
keyboard, read and write files to the disk system, and send messages to the
screen, it doesn’t carry out these actual in p u t/o u tp u t operations itself. For
that, it must call on the next part of the C P /M operating system, the BDOS.
The BDOS, for “ Basic Disk Operating System,” is located ju st above the
CCP in memory. BDOS handles all requests for input and output made by
your program. This includes the reading and writing of information from and
to disks, the maintaining of a directory of disk files, and the allocation of the
space that these files occupy on the disk.
BDOS also acts as a sort of intermediary for system calls that you make to
nondisk devices such as the keyboard and the video console. Sometimes
BDOS does not do very much with these calls itself, but merely passes them
along to the BIOS portion of C P /M (which we’ll describe next). Other times,
it needs to do considerable work to prepare data for the BIOS. For instance,
if you tell BDOS to print a string of characters on the video screen (the “Print
String” system call), BDOS will break the string up into individual characters
before sending it on to the BIOS, since the BIOS driver routine only deals
with one character at a time.
Like the CCP, BDOS is entirely independent of the particular computer or
disk-system it is being run on, so it does not have to be changed when it is
moved to a different system.
Finally, we come to the part of C P /M which actually communicates with
the outside world: the BIOS (for Basic Inp u t/O u tp u t System). The BIOS, as
we’ve mentioned before, contains the subroutines that actually communicate
with I/O devices like the disk drives and the console and the printer. It is
22
The Big Picture
■Direct connection
to I/O devices.
23
Soul of C P /M ®
(For example, BASIC can look at these locations and determine how large a
user program it can accept). We’ll cover other uses of page zero as we explore
the workings of the disk system.
8080 ARCHITECTURE
In this section, we’re going to describe a few fundamentals about how com
puters work on the assembly language level. (If you’re already familiar with
assembly language, you can skip to the next section.)
For a programmer, a computer can be thought of as consisting of two
parts: the memory and the CPU. The CPU, or Central Processing Unit, con
tains a number of registers. We’ll talk about memory first and, then, describe
what registers are and what they do.
Memory
I
/ / 2000 hex
/ 2001 hex
43
/
Memory 2002 hex
locatio ns 41
/ 2003 hex
54
/ 2004 hex
/
Addresses of
— memory
locations.
(M emory co n tin u e s up to FFFF hex.)
F ig.1-4. Memory.
54 is the value for “T.” The ASCII code is simply the way that the computer
stores characters. (It stands for “American Standard Code for Information
Interchange.”) Since the computer must always think in terms of numbers, it
translates all characters (letters, punctuation, etc.) into two-digit hex num
bers.
Numbers that aren’t ASCII characters can also be stored in memory. One
byte (one memory location) can hold a number between 0 and 255 decimal (0
to FF hexadecimal). Two bytes (two memory locations) used together can
hold numbers from 0 to 65535 decimal (0 to F F F F hex.)
One of the im portant differences between assembly language (we’ll abbre
viate it A-L from now on) and a high-level language, such as BASIC, is that
when you program in the higher-level language, you don’t need to know
exactly where in memory a particular program or variable is stored. The lan
guage processing program (such as the BASIC interpreter) takes care of
deciding where to put the program and its variables. In A-L, on the other
hand, the programmer must decide himself where to put everything—this
instruction will go in this memory location, that variable will go in that mem
ory location, and so on. For this reason, all the addresses (which is the same
as saying memory locations) are numbered, starting at 0 and going up to
F F F F (hex).
25
Soul of C P /M ®
Registers
D1
/
/
0192 hex
0191 hex
r
POP D
instructions
CD
/
0190 hex CALL 5
Memory
05
locations /
018F hex
00
/ MOV E.A
018E hex
5F
/
Addresses1
of memory
locations.
(Memory continues up to FFFF hex.)
26
The Big Picture
pairs of memory locations. The diagram given in Fig. 1-6 shows the principle
8080 registers.
D on’t worry if all this talk about registers seems a bit obscure at this point.
In the next chapter, we’ll introduce you to some specific operations with reg
isters and their uses will become clearer. For a more complete picture of the
registers, look in Appendix C for a “Summary of 8080 Instructions.”
To summarize, the principal parts of the 8080 microprocessor are seven
registers and a large number (up to 65536) of memory locations. This is
shown in Fig. 1-7.
f= ^ = Q
Continues
down to 0000.
Registers
A
/
B c Memory
D E
H L
Continues up to FFFF.
28
The Big Picture
10010001 91 SUB C
30
CHAPTER 2
In this chapter, you’re going to begin your journey into the mysterious and
exciting world of C P /M system calls. System calls, as we mentioned in the
last chapter, are links between your program and BDOS, C P /M ’s Basic Disk
Operating System. BDOS examines your system call and then uses one of the
Soul of C P /M ®
The first system call that we’ll learn is a simple one called “Console O ut
put.” This call is nothing more than a way to send a single character from
your program to the C P /M display screen. It’s like a one-character-at-a-time
version of the PR IN T statement in BASIC. We’ll describe how the call is
used, then write a program that makes use of the call, and, finally, show you
how to type the program into the computer’s memory and execute it using
DDT.
In order to execute this system call, your assembly language program must
do three things:
T hat’s all there is to it! If you do these three things, a character will be
printed on the screen. You don’t need to understand anything about the
actual instructions that C P /M uses to send the character to a particular
kind of terminal or crt screen, since the routine in BIOS takes care of that
for you.
Briefly, here’s what each step does. First, the num ber “ 2” is the number
of the system call in hex. C P /M uses the C-register as the “ mail-box” for a
program to tell C P /M what systems call it wants to use. Second, the ASCII
code for any character can be looked up in Appendix D found at the back
of this book. In this system call, C P /M gets the ASCII value of the charac
ter that is to be displayed from the E-register. And, finally, all system calls,
no m atter what they are, use a CALL to location 5 in order to enter BDOS,
where the BIOS and BDOS routines will do whatever input or output func
tion has been requested; in this case, printing a character on the screen.
All right, you ask, how do I actually go about writing down these steps in a
form that the computer can understand? Let’s look at a program that does
just what we want:
mvi c,2
mvi e,48
call 5
Well, it’s short enough, but what does it all mean? Easy. The first instruc
tion puts the number 2 in the C-register. The second instruction puts the
number 48 in the E-register. And the third instruction causes the program to
“call” or jum p to the entry point of BDOS, which is at memory location
0005.
All the numbers used in programs in this chapter are in hexadecimal, so
the 2, the 48, and the 5 are all hexadecimal numbers. (The 2 and 5 are the
same as their decimal equivalents, but the 48 is equal to 72 decimal.)
Statement “Fields”
The first field shown is the “operation” field. The instruction, or what
you’re going to do goes in this field; “mvi” and “call” are instructions. (To
confuse the issue, the word “instruction” is also used to refer to the entire
statement.)
Following the space (which could be several spaces, or a tab) comes the
“operand” field. This field contains the thing you're going to do it to. Thus, in
the first line, “mvi” is the instruction or operand. It operates on the operand
field, which contains “c,2”, and causes the number 2 to be placed in the C-
register. In the third line, CALL is the instruction, and it operates on the
operand 5, causing the program to jum p to location 5. Later, we’ll learn
about other fields in the instruction line, but for the time being these two will
keep us busy.
Something to notice here is that the order in which things are written in the
operand field may seem backwards. In the example, “MVI C,2”, it’s the 2
that is placed into the C-register, not the other way around. It’s like the state
ment “ LET C = 2” in BASIC, where the variable C is given the value 2.
/ 1 / 00FF
/
Memory
address.
/ / j
00FF
/
0100
0E MOV C,2
C-register /
0101
02
( °2 0 " /
0102
The number
2 is moved /
from memory
to the C-register.
part of the instruction. This number will vary, depending on which of the
seven registers the constant is to be placed in. The constant itself is given in
the second byte of the instruction, at location 101; it’s the hexadecimal
number 02.
We’re not going to be too concerned with the exact hexadecimal codes for
the instructions we learn, but it’s im portant to understand the relationship of
the codes to the symbolic instructions that we’ll be typing in using DDT.
We’ll type in a symbolic instruction, like “mvi c,2”, and D D T will take care
of the dirty work of figuring out the corresponding hexadecimal code and
35
Soul of CP/M®
placing it in memory. We’ll talk more about this process later, when we show
you how to type in the program.
Examples:
mvi c,2
mvi h,ff
mvi e,20
So the instruction “mvi c,2” means “take the number 2 and put it in the C
register.” This 2 in the C-register tells BDOS that we want to execute Func
tion 2, which is Console Out.
The number 48 (hex) is the ASCII value of the letter “ H ” (you can check
this in the table of ASCII values in Appendix D). So the instruction “mvi
e,48” means “take the ASCII value of ‘IT and put it in the E register.” Note
that not all hex values represent printable characters. If you put a 0 in the E
register, for example, nothing would happen when you tried to print it,
because 0 is the ASCII code for a “null,” which is a nonprintable character.
Also, remember that this number must be the hex representation of the char
acter and not the decimal representation.
/ /
0103
/
0104 00
/
The return • CALL 02C0
address, 107, 0105 CO
will be placed /
on the stack instruction
0106 02 is executed,
A the program
/ / \ will go to
location 02C0.
/
The “Stack”
02BF
02C0
02C1
Examples:
call 5
call 100
call bfOO
So, in our little program, the “call 5” means “execute the subroutine at
memory location 5 hex” and 5 hex turns out to be the entry point for all
system calls. W hat really happens is that locations 5, 6, and 7 contain a jum p
instruction to the actual BDOS entry point in high memory. We’ll talk more
about that later in the book. But for now, all you need to know is that in
order to do a system call, you execute a CALL 5 instruction.
So there it is, your first C P /M system call, a simple 3-instruction program,
written in assembly language. But, how do we get it to do what it’s supposed
to do—put a character on the screen? In other words, how do we put the
program into the com puter’s memory and execute it?
We could actually write the program in “official” assembly language, using
the ASM assembler program that comes with C P/M , and, then, execute it as
a COM file (the same way systems programs are executed). But if we did
that, we would have to add other instructions to the program to make it work
37
Soul of CP/M®
This program is very much like the last one, except that we print two letters
instead of one. In other words, we use the Console Out system call twice,
once with “ H ” and once with “I.”
Something to note here is that we have to restore the “2” in the C-register
(as well as putting the new character in the E-register) before we can “call 5”
the second time. This is because the system call itself trashes the contents of
the C-register. (“Trash” is a programmer’s word meaning to change some
thing, usually with disastrous results.)
Also, we’ve added comments to each line, to make the program clearer
(although you can’t actually type in such comments in DDT). And, there is a
final addition to the program —the “rst 7” instruction at the end.
This instruction was actually designed to be used with the interrupt system
of the 8080 chip. Since this book does not cover the interrupt system, we
won’t say anything further about RST, except to note that, since D D T uses
the interrupt system, this instruction must be used to terminate programs
when they are being executed under DDT. When used in DDT, RST returns
control from the program to the D D T monitor. It’s a little like the STOP or
END instruction in BASIC. W ithout the rst 7, the computer would just keep
38
One Toe in the Water
on executing all the instructions in memory that happened to follow the end
of the program.
Example:
rst 7
The first step in writing our program is to load and get into DDT. (Notice
that our typed input will be in lowercase letters, while C P /M ’s output is in
uppercase letters. D on’t worry about this. C P /M is good at translating the
lowercase input to uppercase.) After you load D D T by typing “d d t” follow
ing the “A > ” prompt, D D T will print a “sign-on” message and then print a
dash (“-”) and will wait for your command. (The dash is the D D T
prom pt character.)
You can now type “alOO”, which means “ start assemblying a program at
location 100 (hex).” D D T will respond to this command by printing 100,
which is the location where the next instruction will go in memory. Each time
that we type in an instruction and hit the carriage return, D D T will reply
with the next available memory location. Here’s how our program will look
when typed in using DDT:
A>ddt
DDT VERS 2 .2
-a 1 0 0 --------------- Assemble code at 100 hex. (Type each instruction, followed by a
0100 mvi c ,2 return.)
0102 mvi e ,4 8
0104 c a l l 5
0107 mvi c ,2
0109 mvi e ,4 9
01 OB c a l l 5
010E r s t 7
01 OF ----------------- Press return to end assembly.
39
Soul of CP/M®
We began at 100 hex because that’s the standard beginning address for all
C P /M programs and that is where the SAVE utility looks for code to save.
(We’ll talk about that later.) Notice how different instructions take up differ
ent amounts of memory: mvi takes two bytes, call takes 3, and rst only needs
1. When we’ve typed the last instruction, we type a carriage return instead of
another instruction to let D D T know that we’re done.
Let’s make sure the code is set up right by using the “1” (lowercase “L”) list
command. The “1” command prints out or “lists” a program in the same
format that we typed it in.
Looks great. If you made any errors in entering your code, use the “A”
(assemble) command again to reenter the correct code. For example, if you
mistakenly typed CALL 6 at line 104, you would change it by typing: “a 104”
(return). The address 0104 would appear. Then, you would simply type “call
5” , hit return twice, and you’re finished:
-1100
0100 MVI C,02
0102 MVI E,48
0104 CALL 0006 W oops-typed the wrong thing.
0107 MVI C,02
0109 MVI E,49
010B CALL 0005
010E RST 07
40
One Toe in the Water
Now that our little program is entered into the com puter’s memory, it’s
time to actually execute it, using DDT. We use the “go do it” command “g”,
followed by the address where our program starts (100 hex).
Wow! It actually printed what it was supposed to! The asterisk tells us that
the program is finished, and the 010E tells us that the last instruction to be
executed was a “rst 7” at location 010E. The HI, of course, stands for “High
ly Ingenious.”
To reward yourself for successfully writing and executing your first C P /M
program, why not take the rest of the day off? Or, at least treat yourself to a
beer!
Now that you’ve written the program, you will want to be able to save it
onto disk so that you can use it later. Here’s how to do it:
If you want to reload the program, you can do it at the same time that
you’re loading D D T :
A>ddt t e s t . d d t
-g100
There is more you should know about the way C P /M handles the Console
Output function. As we mentioned earlier, BDOS does not simply pass the
request for output along to the in p ut/output routine in BIOS—it does some
interpretation of its own. This gives it the opportunity to add some useful
features to the input/output routines that can be used by any program run
ning in the C P /M environment.
For example, when you use the Console O utput system call, BDOS checks
to see if you’ve typed a “control-s” , which has the effect of starting and stop
ping the scrolling of material displayed on the screen. (Control-s is the char
acter generated when you hold down the “control” or “alt” key, and type “ s” .
You’ve probably used it in such C P /M utilities as “TYPE” .) So if you press
control-s as the program is running, C P /M will freeze your output on the
screen—that is, stop runn in g -u n til you press another control-s.
Want to see control-s work on our sample program? Unfortunately, the
program prints “H I” and returns to D DT so fast that you don’t have time to
type anything. The solution to this is to replace the “return to D D T ” instruc
tion—“rst 7” —with a jum p to the beginning of the program, so that we have
an endless loop printing the word HI. Then, you can try the control-s and
verify that it freezes the program, and restarts it as well.
42
One Toe in the Water
The JM P Instruction
/ /
01D1
/
01D2
/
01 D3
/
01D4
/
Examples:
jm p 100
jm p bfOO
You may have noticed something a little strange if you’ve been examining
the diagrams of the programs carefully. Whenever there’s an address in a
program listing or diagram, it seems to be backwards. An address consists of
two bytes. For instance, 01D2 is stored in the com puter’s memory as the two
bytes 01 and D2. However, the makers of the 8080 (and the 8085, Z-80, and
so forth), for reasons best known to themselves, chose to put the least-signifi
cant byte first, followed by the most-significant byte. That is, if you have the
43
Soul of CP/M®
Notice the NEXT and PC headings that D D T prints when you load a pro
gram. NEXT means the next memory location after the one loaded in. Since
we SAVEd one 256-byte page when we saved our program, 256 decimal bytes
(which is 100 hex bytes) are loaded in along with DDT. This fills memory
locations from 100 to IFF, so the next available one is 200 hex. PC means
Program Counter. This is D D T ’s way of keeping track of where it is in a
program, and it is always at 100 when a program is first loaded.
We want to change the last instruction in the program, RST 7, to a JM P
100. So we type:
44
One Toe in the Water
e tc .
A huge amount of H is are sent to the screen, quickly filling it up. The pro
gram will continue to send H is until the control-s key is pressed; at which
point, the display will freeze. Pressing another control-s will allow it to restart.
But how do you stop the entire program, so you can get back to D D T or
C P/M ? Unfortunately, you’re in big trouble in this regard. The only way to
regain control of the computer is to hit the reset button. That will cause a
“cold boot,” and you’ll be back in C P/M . The moral of this is to avoid writ
ing assembly language programs with loops that never terminate. In BASIC
you can always hit the Break key but, in assembly language, you need to
write an escape route into your program. In the next section, we’ll show you
how to modify your program so that it can be interrupted from the keyboard.
So, you’re now an expert on the C P /M function call “Console O utput.”
The nice thing about this is, as we mentioned earlier, that what you’ve
learned is applicable to any machine running C P/M , whether it has an 8080
chip, an 8085, a Z-80, or whatever. It’s also applicable to any sort of display
terminal, whether it’s from TeleVideo, IBM, Amdek, or any of the dozens of
other manufacturers.
There are many other nondisk C P /M system calls, some of which are more
powerful than this one, and some of which are pretty mundane. U nderstand
ing them all will help you discover how C P /M works and how to write pro
grams that make use of C P /M ’s full capabilities.
The next C P /M function call that we will study is a simple but im portant
one that is used by almost all of the C P /M utilities.
45
Soul of CP/M®
A>ddt t e s t l . d d t
NEXT PC
0200 0100
- L1 0 0 List the code to see if it’s all right.
0100 MVI C,02
0102 MVI E,48
0104 CALL 0005
0107 MVI C,02
46
One Toe in the Water
Looks fine—an endless loop that prints HI on the screen forever, or at least
until RESET is pressed causing a cold boot of C P/M . N ot the most elegant
way to end the program.
Change your code as follows:
-a10e
01 0E mvi c , b Put 0B hex in C register.
0110 call 5 Call BDOS.
0113 ora a O R A with itself—to set zero flag.
0114 j Z 100 Go do HI again if no key pressed.
0117 rs t 7 Back to D D T if key is pressed.
0118
-L100
0100 MVI C,02
0102 MVI E,48
0104 CALL 0005
0107 MVI C,02
0109 MVI E,49
010B CALL 0005
010E MVI C,0B
0110 CALL 0005
0113 ORA A
0114 JZ 0100
0117 RST 7
0118
You can save this program as “ test2.ddt” and then bring it back into mem
ory with D D T in the usual way:
-go
A>save 1 t e s t 2 . d d t
A>ddt t e s t 2 . d d t
47
Sou! of CP/M®
When we use the “Get Console Status” system call, the A-register comes
back with an 8-bit quantity in it, and we want to find out if it’s zero or not.
The way to do this is to test the zero flag, but (remember this!) the zero fla g is
not set until we perform an arithmetic operation. So we need to do some arith
metic on the A-register which will set the zero flag if the A-register is zero. An
old programming trick here is to OR the A-register with itself.
48
One Toe in the Water
“O R ” means to take the bits in the A-register and OR them with the corre
sponding bits in some other register. This is illustrated in Fig. 2-4. Either the
B, C, D, E, H, or L register will work fine. As you no doubt recall from
BASIC:
0 ORed with 0 is 0
0 ORed w ith 1 is 1
1 ORed with 0 is 1
1 ORed with 1 is 1
A-register Some other register
01001100 00001111
01001100
00001111 Contents of
A-register ORed
01001111
with contents of
another register.
A-register
Examples:
ora b
ora h
zero” (jz) instruction takes care of it nicely by doing just what its name
implies.
The JZ Instruction
0102
/
Goes back to 100
iiiiiiiiiiiiiiini it zero flag set.
/
0114 C3
/ JZ 1 0 0 -
0115 00
Goes on to 0117
0116 01 if zero flag
/ not set.
0117
/
Examples:
jz 100
jz bfOO
In our case, if the A-register returns with zero, it means no key was pressed,
so the zero flag will be set to 1, the jz 100 instruction will cause program con
trol to go back to 100, and the program will continue to print “H I” on the
50
One Toe in the Water
screen. However, if a key is pressed, the A-register will return with nonzero,
the zero flag will be cleared (set to 0), and when we execute the jz 100 instruc
tion, we won’t jum p at all but, instead, will go to the last instruction in the
program (the “rst 7” instruction) which will return us to DDT.
So, does all this actually work?
-g 1 00 Execute th e p ro gram .
H I H I H I H I H I H IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH IH I
H IH IH IH IH IH I H....etc.
___ H IH IH IH IH IH IH IH IH * (0 1 1 7 )
-z
------------------------- *-----------------------N
We pressed a key here and the program stopped.
Here we are, back in DDT! Lo and behold, our modified program works
too! It’s not perfect, because the letter we pressed gets passed along to DDT.
T hat’s the “z” you see printed out following the D D T prompt. If we type a
carriage return, D D T will think the “z” is input and will output a question
mark when it can’t understand it. Aside from this minor glitch, the program
is certainly very successful in getting us out of the endless loop.
Congratulations are again in order. You have just created your third
C P /M program and learned a new system call! At this rate, you’ll be writing
programs for mass consumption within days!
again. We’ll use “Console O utput” to send the characters to the screen and the
“Get Console Status” to end the program if a key is pressed. (W ARNING: Do
not turn on your printer while running this program as it does not send a
carriage return/linefeed to the screen, and it will, therefore, cause the printer
to go to the right edge of the paper and just sit there, typing over and over on
the same spot.) This program uses several new and interesting instructions and
it introduces you to that useful but mysterious device, the “stack.”
Again, get into D D T and enter this program with the A (assemble) com
mand:
-a100
0100 mvi e ,2 0 Set up E-register for first ASCII character.
0102 mvi c ,2 Ready for output.
0104 push d Save the DE register-pair (save E).
0105 c a ll 5 Send character to the screen.
0108 pop d Get the DE pair back (E is restored).
0109 in r e Bump E by + 1 .
010A mov a ,e Put the E-register in the A-register.
010B cpi 7f Is it the 127th character?
010D jn z 102 If result not zero, then no, so loop.
0110 mvi c , b Check console status.
0112 c a ll 5 Call BDOS.
0115 o ra a Set zero flag based on contents of A.
0116 j z 100 If a = 0, then no key pressed, so loop.
0119 rs t 7 Return to DDT.
A>save 1 b a r b e r . d d t
The program can be executed in the usual way by loading it along with D D T :
A>ddt b a r b e r . d d t
And, typing:
-g100
But, before you do that, take a minute to understand just what’s going on.
There are a lot of new instructions in the program that you need to learn.
(Too late, right? You already ran it. That’s all right, we admire impetuous
programmers.)
52
One Toe in the Water
Fig. 2-6 gives a flowchart of the program’s operation. You can refer to the
flowchart as you read about the new instructions the program uses.
A flowchart is simply a pictorial representation of the operation of a pro
gram. Sometimes, certain conventions are followed in flowcharting. For
instance, actions which result in the program making a choice between two
different routes are placed in diamond-shaped boxes. Rectangular boxes
show actions which don’t result in a choice; there’s only one way out of a
rectangle. Circles show places where the program enters or leaves the chart.
The Stack
54
One Toe in the Water
POP
A register pair.
7 Top of the stack.
1020
./
9E03 FCOO
POP
POP
FC00 40 EO
¥
Finally, we can take off the FC00.
each box in the diagrams stands for two memory locations, since each is hold
ing a two-byte quantity.
We can’t take a number out of the middle of the stack. If we want the
number FC00, for example, we must first remove the 9E03, then the 1020,
and finally the FC00. This is illustrated in Fig. 2-8.
Remember how, in the last chapter, we mentioned that the B and C regis
ters, the D and E registers, and the H and L registers could be put together to
form the BC, DE, and HL register-pairs? It’s the contents of these 16-bit
register-pairs that are stored on our stack. Sometimes these 16-bit quantities
are simply numbers that we want to save, other times they are addresses.
W hat does the stack consist of? It’s ju st a series of memory locations,
somewhere in your computer’s memory. Often the program you’re using,
55
Soul of CP/M®
such as DDT, or C P /M itself, takes care of figuring out what memory loca
tions are to be used for the stack, and we’ll assume that’s true for the time
being. Later, we’ll find that this can sometimes be a dangerous assumption,
and we’ll figure out ways to deal with the stack more directly.
How does the CPU know where in memory to put whatever is supposed to
go on the top of the stack? Well, there’s actually another register, which we
haven’t mentioned yet, called the “stack pointer,” which keeps track of where
the top of the stack is. For the moment, we won’t need to know too much
about this register since the common instructions for putting things on the
stack and taking them off—PUSH and POP—handle the stack pointer auto
matically.
Something strange to notice about the stack is that it grows downward in
memory. T hat is, if the “ top” of the stack happens to be at location 1000
hex and you add something to the stack, it will go into locations F F F and
FFE, ju st below 1000. The next thing you put on the stack will go in loca
tions F F D and FFC, and so on. Likewise, if the top of the stack is at 1000
and you take off the first item, it will come from locations 1000 and 1001.
The next item will come from locations 1002 and 1003, and so on. There’s a
reason for this seemingly backwards behavior, and we’ll discuss it later in
the book.
The PU SH Instruction
push x
where the x can stand for either “b”, “d”, or “h” . In this case, “b ” stands for
the BC-register, “d” stands for the DE-register, and “h” stands for the HL-
register. When this instruction is executed, the 16-bit (two bytes, four hex
digits) contents of the register-pair x are copied into the vacant memory loca
tion at the top of the stack.
This is the memory location pointed to by the stack pointer register. Once
the quantity is written into this location, PUSH takes care of changing the
stack pointer register so that it points to the new top of the stack -th e next
available place where a quantity can go.
Examples:
One Toe in the Water
Before the PUSH Instruction:
The Stack.
Stack pointer.
/
OFFB 98 Top of memory.
PUSH
/
OFFC DE
/
OFFD 90
/
A register pair.
OFFE 77
/
98DE OFFF 0E
/
1000 D9
/
The Stack.
The PO P Instruction
POP is simply the opposite of PUSH. It takes things o ff the stack, from where
PUSH put them on the stack. The 16-bit quantity removed from the stack is
written into the register-pair specified in the operand field of the instruction:
pop x
where x can be either “b ”, “d”, or “h” and stands for the BC, DE, or HL
registers.
POP takes care of incrementing the stack pointer so that it points to the
next free memory location.
57
Soul of CP/M®
Before the POP Instruction:
Stack pointer.
/ — /]
0FFB 98 Top of memory
/
0FFC DE
,
OFFD 90
A register pair. /
OFFE 77
/
OFFF OE
1000 D9
/
The Stack
Stack pointer
points here.
A register pair.
OFFE 77
Z I=
98 DE OFFF OE
1000 D9
The Stack
Examples:
pop d
pop h
And, every time we call the Console Out routine, we want to increase the
ASCII value of the contents of the E-register by one, so as to print the next
character. Unfortunately, however, the Console Out routine trashes
(destroys) the contents of the E-register when it is called. To prevent this
unfortunate occurrence, we save the E-register on the stack with a “push d”
instruction before calling “Console O utput,” and restore it afterwards with a
“pop d.” Notice that, even though it’s the E-register we want to save, we use
“d” in the PUSH and POP instructions, because it is the first letter in the
register-pair “DE.” The instruction saves both the “ D ” and “E” registers but,
in our case, the “ D ” register is ju st along for the ride.
We mentioned that we wanted to increment the ASCII value in the E-
register each time that we call the Console Out routine, so that we will print
all the ASCII characters in order. How do we go about incrementing (adding
1 to the contents) a register?
Examples:
inr e
inr a
inr h
59
Soul of CP/M®
You’ve already learned that the MVI instruction will take a fixed 8-bit
number from memory (from the location immediately following the instruction)
and put it in a register. The MOV instruction, on the other hand, takes the 8-bit
contents of a register and puts it in another register. MOV can be used to MOVe
data from any 8-bit register to any other 8-bit register. The format is:
mov x , y
where the contents of register “y” is moved into register “x.” (As we men-
tioned before, this may seem backwards, or at least a little arbitrary, but
you’ll get used to it. Think of the data as going from right to left in the
instruction. All the 8080 instructions do things from right to left in this way.)
In the diagram of Fig. 2-12, the contents of the B-register are copied into
the E-register by the MOV instruction. The contents of the B-register are not
changed.
This instruction does not cause any flags to be set. Thus, if you MOV zero
into a register, the zero flag will not be set.
B-register E-register
A A
82
)
Fig. 2-12. The MOV in stru ctio n .
After the MOV Instruction:
B-register E-register
/ A
82 82
MOV E,B
)
Examples:
mov e,a
mov h,l
mov d,b
We mentioned earlier that not all hex numbers are printable as ASCII
codes. In fact, the printable ASCII codes run from 20 (hex), which is a space,
to 7F (hex), which is the rubout. Sending numbers greater or less than these
to your console device or printer is likely to cause strange and unpredictable
60
One Toe in the Water
results. Thus, we want to start our program by sending 20 (hex) to the con
sole, and when we’ve sent 21, 22, and so on up to 7F, we want to start over
again with 20. The first instruction in the program, “mvi e,20”, starts us off
with 20, but how will we know when we get to 7F?
The answer is the CPI instruction, which stands for “Compare Immedi
ate.” CPI performs a comparison between the number in the A-register and
the number in memory immediately following the CPI instruction. The result
of the comparison is used to set the various flags, including the zero flag.
How do we know what flags will be set? The idea here is to think of this
instruction as a sort of “phantom ” subtraction of a fixed 8-bit quantity from
the A-register. Why isn’t it a “real” subtraction? Because the quantity in the
A-register is not actually changed; nothing is subtracted from it. However,
the zero flag (and the various other flags that we will learn about later) act as
if the subtraction had been carried out.
This is easy to understand in the case of the zero flag: i f the two numbers
are equal, the zero fla g is set. Why? Because when you subtract a number from
the same number, the result is zero.
Section of memory where
the program is located.
Examples:
cpi 7f
cpi 2
61
Soul of CP/M®
In our case, the “cpi 7f” instruction compares the contents of the A-regis
ter with 7F hex. The first time through the loop, the A-register will contain
20, because the E-register contains 20. So the result of the subtraction will
NOT be zero. The next time, the A-register will contain 21, because the E-
register contains 21, because we incremented it with the IN R instruction. The
next time it will contain 22, and so on, until we’ve counted up to 7F. When
the A-register is 7F, the results of the comparison will be 0, and the zero flag
will be set. W hat use will we make of the zero flag?
/ /
0100
/
0101
/
0102
/
lllllllllllllllllllll
lllllllllllllllllllll
/
Goes back to 0100
0114 C2 if zero flag is NOT set.
i
JNZ 100 —
0115 00
/
Goes on to 0117
0116 01
if zero flag is set.
/
0117
/
Examples:
jnz 100
jnz bfOO
In the case of our barber-pole program, the CPI instruction will result in
the zero flag not being set until all the ASCII characters from 20 to 7F have
62
One Toe in the Water
been printed. So each time the JN Z instruction will take us back to the sec
ond instruction in the program —at location 102. When we’ve printed all the
ASCII characters, the program will go on to location 110, where it will per
form the “Get Console Status” system call to see if any key of the keyboard
has been pressed. If not, it will start the program over. If so, it will return to
DDT, as in the example shown in our previous program.
When you run the program, you should get something like this display:
A>ddt b a r b e r . d d t
-g 1 00
This is a good program to keep in your wallet and memorize for when
you’re at a party or at a new friend’s house and you want to impress them
with your knowledge of C P /M and 8080 code.
CONSOLE INPUT
Console Input is perhaps the most-often used C P /M function call. Its pur
pose is to get a character from the keyboard into your program. Console
Input is used by almost all programs that run under C P /M which request
user input from the keyboard. For example, all text editors for C P /M use the
Soul of CP/M®
console input function to get an ASCII value from the keyboard. It works
much like the G ET statement in BASIC. When Console Input is called, it
reads the next console character into the A-register of the 8080. It will “echo”
your character to the screen as well. It will not echo control characters but
will react to many of them. If no key is pressed, the function will hang, wait
ing until a key is pressed, and, thus, suspending execution if a character is not
ready.
There are some special features of the Console Input function that you
should be aware of. Most im portant is the way it responds to C P /M control
characters. It does not respond to a warm boot (control-c), or to the printer
stop/start toggle (control-p). This makes complete sense, as you often
D O N ’T want the user to be able to warm-boot the system or turn on the
printer from inside your program. When you DO wish the user to have such
control, you can use another C P /M console function called “Read Console
Buffer.” This call, which we will describe shortly, is the one that comes with a
complete set of editing commands, and is used by many of the C P /M utili
ties. You’ll hear more about “Read Console Buffer” later.
Beep Program
Let’s do a simple exercise program with the Console Input system call.
This program will cause C P /M to echo everything typed in at the keyboard
onto the screen, accompanied with a little beep that is provided by your con
sole’s “beep” or “bell” sound.
Bring up D D T and enter this short program.
- a 1 00
0100 mvi c ,2 Set up for console output.
0102 mvi e ,7 a s c i i 7 = bell.
0104 c a ll 5
0107 mvi c,1 Set up for console input.
0109 c a ll 5
010C jmp 100 Loop forever getting key and echoing it.
Save it with:
-gO
A>save 1 t e s t 3 . d d t
A>ddt t e s t 3 . d d t
and
-g100
Try it out. Each time you press a key, the console beeps and the character
is printed on the screen. Neat. But if you try to press control-c to reboot,
nothing happens. G reat Scott, we’re caught in a deadly endless loop . .. ;
we’ll have to press reset to escape. But first, while you have your program
running, try a few of these keys and verify what happens on your system. All
should cause a beep.
W hat this tells us is that the Console Input function does not respond to all
the normal C P /M control-key conventions. If you wanted it to respond, for
instance, to the control-c key, you would have to write a special part of your
program yourself, to do just that.
Also, you can see that our program has a “bug”in it, in that the only way
we can turn it off is to do a cold boot by resetting the system.This, again, is
less than elegant. Can you think of a way out of this problem? Could you
rewrite the program so that it looks for the control-c and causes the program
to end if it sees one? Yes, Watson, and here’s how to do it!
Take our old code:
-a10C
010C c p i 3 Check if A = 3.
010E jn z 100 If not, repeat loop.
0111 r s t 7 Yes, it was a 3, so end.
So far we have executed all our programs from D D T by typing glOO. This
is fine for short exercises, but many times we want to be able to execute a
program directly from C P /M without calling up D D T at all, simply by typ
ing the name of the program following the C P /M prom pt A > .
In theory, this is simple. We write the program in DDT, then SAVE it as a
COM file, and, then, execute it directly from C P/M . However, in practice,
there’s a problem, and it’s this: if our program contains a “rst 7” instruction,
and we attempt to execute it directly from C P/M , we’ll probably “crash the
system” (cause error messages followed by a cold boot), because C P /M does
not respond to “rst 7” the same way that D D T does, “rst 7” can be used only
to return to DDT. To return to C P/M , we use another instruction—“ret” .
RET is usually used in connection with the CALL instruction. CALL takes
you from your main program to a subroutine, and RET takes you back again.
Remember how, in the CALL instruction, the address immediately following
the CALL was placed on the stack? Well, the RET instruction takes this
66
One Toe in the Water
address off the stack and transfers control back to this address in the main
program.
program.
Example:
ret
A>save 1 te s t4 .c o m
Now you can execute the program directly from C P/M , simply by typing its
name:
A > te s t4
Type in some stuff. Listen to the beeps. W hat happens when you type a
control-c? You’re back in C P /M again! This is just how programs are exe
cuted in the big leagues.
The System Reset system call is used for causing a warm boot from your
program. The warm boot, as you recall, causes the CCP part of the C P /M
operating system to be reloaded. Also, several locations in FDOS (BDOS
plus BIOS) are reset to their initial values.
The reason that you need to know how to do a warm boot is simple. Some
times you will need as much memory space in the TPA as you can get. As you
know, BIOS, BDOS, and CCP take up room in the top of RAM. It turns out
68
One Toe in the Water
that you can remove the entire CCP from RAM and still use all of the system
calls, provided you don’t wipe out FDOS. When you want to return to
CP/M , you do a “System Reset” and the CCP will be reloaded and FDOS
reinitialized. System reset works just as if you pressed control-c from the
keyboard. You’ll hear the disk click as the CCP is read off the disk into
memory.
Here’s a little program that will cause a warm boot (system reset). But,
look out! You can’t test it from D D T because it will wipe itself out in the
process of resetting the system, thus, resulting in error messages and trouble!
-a100
0100 mvi c , 0 Set up for system reset.
0102 c a l l 5 Call BDOS.
0105 r e t Return to CP/M .
-gO
Enter the program from DDT, exit D D T with a gO (which also causes a
warm boot and replaces D D T with the CCP), and do a:
A>save 1 te s t5 .c o m
Now you know how to make your program go back and reinitialize C P /M
whenever you want. That means that you can run programs that use the
memory space usually occupied by the CCP and can be assured that you can
reinitialize the system later.
By this time, you know more about C P /M and 8080 programming than
you ever expected to. You’ve learned the Console Out, Get Console Status,
Console Input, and System Reset systems calls. And, you’ve learned a whole
batch of 8080 instructions: MVI, CALL, RST, JMP, ORA, JZ, PUSH, POP,
INR, MOV, CPI, JNZ, and RET.
69
Soul of CP/M®
70
CHAPTER 3
Getting in Deeper
Advanced Console System Calls
In this chapter, we’ll look at some console in p u t/o u tp u t system calls that
operate on whole groups of characters, rather than on one character at a time
as did the system calls in the last chapter. Then, we’ll introduce you to an
unusual display program which will act as a review of what you’ve learned so
71
Soul of CP/M®
far. We’ll describe “Direct Console I/O ,” which lets you interact much more
directly with the screen and the keyboard than have our previous system
calls. Finally, to wrap up our study of nondisk system calls, we’ll cover a
number of functions which deal with nonstandard I/O equipment.
PRINT STRING
Here’s the way to send complete words or even sentences to the screen
from inside C P/M . In the programming world, as you know, a series of char
acters like a word or a sentence is called a “ string” (it’s just a collection of
strung-out characters). In C P/M , a string may goeth and a string may
cometh, meaning that we can send strings to the screen and we can accept
strings from the keyboard. The Print String function is for making your
string goeth to the console display screen. It’s an advanced version of the
Console Out system call in the the last chapter, which only sent one character
to the screen at a time. Print String is sort of like the PR IN T statement in
BASIC, except it is a bit more indirect to use, as you will see.
Print String expects that you have stored the string you want to send to the
screen in a continuous area of memory as a sequence of ASCII bytes. (We’ll
show you how to do this presently.) You must end the string with hex 24, the
ASCII value for the “$” symbol. Then, to print the string, you put the
address where the string starts in the DE register-pair, put a 9 in your C REG
and do a BONSAI TO BDOS (a very excited call 5).
Of course, manually typing in and looking up the ASCII equivalents for all
of the characters in the message that you want to display can certainly be a
tedious process. Be patient! In the next chapter, we are going to show you
how to use ASM, the C P /M assembler, to simplify this process. When using
an assembler (as opposed to a miniassembler like that in DDT), you can
simply enter the letters for the string into the source listing directly from the
keyboard. So take heart, we’ll eventually learn to do Print String the easy
way. In the meantime, using D D T will be a bit tedious, but it will give us a
better idea of what’s really going on.
72
Getting in Deeper
First use the D D T Set command “s” to put the hex values for the letters
“A B C D E” into memory starting at location 010A hex. The set command
first displays the current contents of the memory location that you are
about to type in to. In this case, these numbers are of no interest to us. Each
time you type a two-digit (one byte) value and hit the carriage return, your
number is stored in the memory location shown, overwriting the old value.
Now you can check that you have entered the string correctly, using the D
command:
-d 1 0 a ,1 0 f
010A 41 42 43 44 45 24 ABCDES
As you can see, the numbers are just as we entered them, with the ASCII
letters that they represent shown in the right-hand display column. Now we
are ready to enter the program that will actually print the string:
- a 1 00
0100 mvi C,9 Set up for Print String.
0102 t x i d ,1 0 a We start our string at 10A hex.
0105 c a ll 5 Our famous BDOS call.
0108 rs t 7 Return to DDT.
Save it as test5.ddt.
73
Soul of CP/M®
As you recall from the last chapter, the mvi instruction took a one-byte
constant and placed it into the register indicated in the instruction. LXI is
similar, except that it puts a two-byte constant into the indicated register-
pair. This two-byte constant is stored in the program in the two bytes imme
diately following the lxi instruction in memory.
As in the MVI instruction, the “ I” in LXI means that the constant to be
stored immediately follows the instruction in memory (as shown in the dia
gram of Fig. 3-1). The “X” means that the instruction operates on a register
Before LXI D.010A is executed:
Section of memory
containing program.
0100
DE register 0101
0102
11
0103
0A
0104
01
/ 0100
0101
DE register
/ 0102
z : 11
010A
k 10103 LXI D.010A
0A
r 0104
The constant in the instruction 01
is moved into the DE register when /
the instruction is executed.
1/
Fig. 3-1. The LXI instruction.
74
Getting in Deeper
pair and not on a single 8-bit register. These are both conventions which
make it slightly easier to remember the mnemonics (abbreviations) for the
instructions.
Don’t forget when using LXI that you need to type in four hexadecimal
digits, not two as with MVI.
Examples
lxi d,1234
lxi h,01ff
lxi b,bf00
-g100
ABCDE*01 08 Worked perfectly! ABCDE got printed.
There is almostno limit to the size of the string you can print. To see this,
use the “fill” command in D D T to put a large number of the same ASCII
letter after the ABCDE string. To use “fill,” type f, followed by the address
where you want to start filling, the address where you want to stop filling,
and the constant you want to fill in. For instance,
-fl Of,400,41
will fill memory from lOf to 400 with ASCII “A’s” . Don’t forget to put the 24
hex at the end o f the string, using the “s” function.
-s401
401 FD 24 This is the ASCII for
402 DO . Period used to terminate “s”.
Now try your program on this new string. The screen will simply fill with
the letter “A ” continually until it reaches the end. D O N ’T TU R N ON
YOUR PRINTER. There are no carriage returns at the end of every 80th
character of the string, so the printhead may go to the right side of the car
riage and bang itself to death there.
75
Soul of CP/M®
One feature that makes Read Console Buffer especially useful is that it
responds to the set of C P /M control-character commands and, thus, per
mits editing while you’re typing in the string. Perhaps the most important
of these commands is control-c. If this is typed at the beginning of the
string, C P /M does a warm boot. Thus, by using Read Console Buffer, you
allow your program user the opportunity to reboot the program during
input. This may be desirable or not, depending on the type of program
being used. Read Console Buffer offers a host of other useful line editing
features. The commands available to the lucky user of this function are
(press CONTROL with all these letters):
We don’t want to go too far off on a tangent at this point by getting too
involved in these editing features. You can practice them on the string that
we will be using in the next exercise. For now, here is a short way to remem
ber the most im portant commands:
You can copy this out and stick it up near your computer, or on the fore
head of a passing co-worker.
The Read Console Buffer system call “reads” a line of edited console input
into a buffer addressed by the contents of register-pair DE. You must set up
the buffer address in the DE-register before making the call. The LXI
instruction is used to do this, ju st as in the Print String function. In addition,
you must set up a number in the beginning of the buffer which represents the
maximum number of characters you want to accept. If you put a hex 20, for
example, in the buffer, then as soon as the user types more than 32 characters
(decimal), the function will terminate. We call this “ending by OVER
FLOW .” The user can also terminate the string by typing a RETU RN (or a
control-J or -M).
77
Soul of CP/M®
/ / / / / / / / ,/|
mx nc c1 c2 c3 ??
? ? |/
-
--T his is the maximum characters allowed.
The buffer that we set up looks like the diagram in Fig. 3-2.
This is what it means. “DE + O” is simply shorthand for the address sent to
the function in the DE-register. DE + 1 is the next address after this, and so
on. Thus, if you put 400 in the DE-register when you call this function,
D E + 1 will be at 401, DE + 2 will be at 402, and so on up to DE + n, which
will depend on the length of your message.
The “mx” indicates the maximum number of characters that the function
will allow to be typed into the buffer, a number from 1 to FF hex (1 to 255
decimal).Your program must put this value in the first position in the buffer.
The “nc” is the number of characters actually typed by the user and this is set
by FDOS when the function returns to your program. This number is useful
for determining how long the input string actually is. It is found at D E + 1.
Following “nc” are the actual characters read from the keyboard. If the
number of characters actually typed by the user is less than the number set
by mx (nc < mx), then the remaining positions in the buffer are whatever
they were before the function was called and have no meaning. These are
marked as ?? in Fig. 3-2.
Note that some control characters typed into the print buffer will get
stored in their proper ASCII codes. Tabs, for example, appear as 09 hex. A
control-c in the middle of a line will appear as a 03 hex.
Our Read Console Buffer example is really quite simple. Let’s assume that
we’re going to start our buffer at 200 hex. Type the following in D D T :
- a 1 00
0100 mvi a , 20 Set max characters to 32 (dec)
0102 s ta 200 and put in first buffer position.
0105 mvi c ,a Set up REG C for Read Console Buffer.
0107 Lxi d ,2 0 0 Load DE with location of buffer start.
010A c a l l 5 Finally, call BDOS.
010D r s t 7 Back to DDT.
78
G etting in Deeper
This instruction will take the 8-bit value in the A-register and store it any
place in memory. Where this value will be stored is determined by the
address in the operand field of the instruction. For instance:
sta 2000
will take the 8-bit value in the A-register, which might be anything from 00 to
FF, and store it in memory location 2000.
In DDT, this address is represented by a four-digit hexadecimal number.
In the next chapter, when you learn how to use the assembler, you will find
that this address can also be represented by a name.
Notice the difference between this instruction and others that we’ve
learned about earlier. For instance, MOV B,A takes an 8-bit value from the
A-register and stores it, not in memory as STA does, but in a register: the B-
register. An instruction like MVI A,7F is different from STA in two ways.
First, it’s loading an 8-bit value from memory into the A-register, not storing
it from the A-register into memory as STA does. Second, MVI A,7F refers to
a constant at a place in memory immediately following the MVI instruction,
while STA refers to a location in memory that can be located far away from
the STA instruction itself. For this reason, STA uses an address in the oper
and field, while MVI uses the actual 8-bit value.
Examples
sta 2010
sta 0100
sta bfOO
Our example program first puts a 20 (hex) into the A-register and stores
it at the beginning of our buffer at 200 (hex). This is “mx,” the maximum
number of characters. The program then calls Read Console Buffer, using
the address 200 as the start of the buffer. It then waits for you to type
something in.
79
Soul of CP/M®
/ 0100
Section of memory
containing program.
/ 0101
A-register
/ 0102
63 IP
/ 0103 STA 2011
/ 0104
/
/
1111111111111
1111111111111
/ / Section of memory
where constant
will be stored.
2011
/ 2012
/
0100
Section of memory
containing program.
A-register 0101
0102
0104
2012
80
Getting in Deeper
There is a small glitch in that the “ *010D”, which D D T prints when the
program is over, overwrites part of the line that we typed in. This would be
easy to fix, as we’ll see in the next example, but for the time being we’ll live
with it.
Now you can dump (display) the buffer at 200 (hex) to see if what you
were supposed to put there has actually arrived.
-d 2 0 0 ,2 1 f
-g 1 00
Now i s th e tim e f o r a l l good men ------- Type this in, keep typing.
* 0 1 0D I------- — At this point, the function took
~ over and returned us to DDT.
Dump the buffer again and there’s our input, safely stored away. This time
the number of characters that were actually typed in is the same as the maxi
mum “mx.”
-d 2 0 0 ,2 2 f
There it is again, just as you typed it. Later we’ll see how a program can
make use of this function to accomplish all sorts of useful and exciting things.
81
Soul of CP/M®
ECHO PROGRAM
Let’s put together the two string-handling system calls that you’ve just
learn ed -P rin t String and Read Console Buffer—into a single short program.
-a100
0100 mvi a , 20 Put max characters at start of buffer.
0102 s ta 1 fe
0105 mvi c,0 a Call Read Console Buffer.
0107 Lxi d , 1 f e
010A ca L L 5
010D mvi c ,2 Use Console Out to print linefeed.
01 OF mvi e,0 a
0111 c a ll 5
0114 mvi c ,9 Print String.
0116 l x i d ,2 0 0
0119 c a ll 5
01 1 c rs t 7 Back to DDT.
011D
you do, Read Console Buffer will terminate itself before you have time to
type the dollar-sign character.
-a100
0100 mvi a ,20 Put max characters at start o f buffer.
0102 sta o lf e
0105 mvi c,0a Set up to get input string,
0107 lx i d ,1 fe store it at lfe.
010A c a ll 5 Call “ Read Console Buffer.”
0100 mvi b,30 Set initial B-register value to 30
010F push b and save on stack.
0110 pop b Get B from stack,
0111 in r b increment it,
0112 mov c,b store it in C,
0113 mov a,b and in A,
0114 push b and put it back on stack.
0115 cpi 50 Is the B-register = 50 yet?
83
Soul of CP/M®
<\J
o
011C mvi (20 hex is a space),
<D
011 E cal L 5 call “ Console Output.”
0121 pop b Get BC, to decrement C.
0122 dcr c Decrement C,
0123 push b save BC.
0124 jnz 115 If C-register not 0, go print space.
0127 mvi c,9 Print the string;
0129 lx i d,200 starts 2 bytes past Ife.
012C ca LI 5 Call “ Print String.”
012F mvi c,b Keyboard character typed?
0131 ca 1 1 5 Call “ Get Console Status.”
0134 ora a Is A-register still = 0?
0135 jz 110 Yes, so do another line.
0138 rst 7 Back to DDT.
You can save this program as “nam edisp.ddt” . Be careful while typing in
the code. One disadvantage of using the microassembler in DDT is that if
you make a mistake in your input, sometimes it’s not easy to go back and
change it. This is because different instructions have different lengths, and if
you put a two-byte instruction in a place where you meant to put a three-byte
instruction, and then you want to go back and try to change it to the 3-byter,
it won’t fit. Using the assembler will eliminate this problem, as we will see in
the next chapter.
Here’s how this program works. When you first start it, you type in your
name. As in the last example, BE CAREFUL! You must terminate your name
with a dollar-sign character!
C-register
y /
99
/
C-register
/ /
98
/
85
Soul of CP/M®
Examples:
dcr b
dcr h
Fig. 3-5 is a flowchart that details the operation of the program. The memory
locations where the different parts of the code occur are shown on the side.
By studying the flowchart along with the program listing, you should begin
to understand how the program works. The heart of the program is the print-
G et input string
(100 to 107)
and store in buffer.
T
Set B-register = 30. (10D)
t
Increm ent B-register. (10F to 111)
Transfer B-register
(112)
to C-register.
( 1 1 A to 122)
(124)
(127 to 12C)
(12F to 135)
(138)
(E xit to D D ^
86
Getting in Deeper
ing of a string of spaces, in lines 11A to 124. Register C is set at the beginning
of this section to the number of spaces to be printed and is decremented each
time until it is zero. Then, the user’s name is printed and the program checks
to see if a keyboard character was typed to halt the program. After this, the
program returns to 10D to increment the B-register, which started off with 30
and will end at 50. The B-register holds the value that is given to the C-
register in line 112 so that the correct number of spaces will be printed each
time through.
Again, don’t worry if every detail isn’t crystal clear. The point really is just
to understand how a number of systems calls can work together in a single
program.
Direct Console I/O provides the serious programmer with a means of get
ting characters from the keyboard and displaying characters on the screen
without echo and without the previously described built-in control-character
functions, which it does not acknowledge. This is useful in those special cir
cumstances where you want to do something nonstandard with the user’s
input or the screen output. For instance, you might be writing a word-
processing program, and you might want to use control-c to cause the cursor
to move to another part of the document, rather than causing a warm boot as
it normally does. Or, you might want to define a special function for control-
s, instead of having it halt the display.
The use of Direct Console I/O lets you define control characters as you
wish and, thus, gives you a great deal of flexibility and control over your
input and output. However, you sacrifice all the capabilities that C P /M
87
Soul of CP/M®
offers for free, such as, freeze the display, warm boot, retype line, backspace,
printer on/off, etc. If you want these functions in Direct Console I/O , you
must build them into your program yourself.
Since using the control characters in a nonstandard way can lead to confu
sion if you’re not careful, the makers of C P /M (Digital Research, Inc.) rec
ommend that you not use Direct Console I/O . Of course, many programmers
use it anyway.
Direct Console I/O is unusual in another way. It’s actually two functions,
accessed with the same system call.
On input, put ff (hex) into the E-register. The function will immediately
return with a 0 in the A-register, and will continue to return 0 until some
thing is typed on the keyboard. Then, it will return the ASCII value of the
character typed, but nothing will appear on the screen. This function is
unlike both the Console Input and Read Console Buffer system calls in that
it does not wait until the user types something before it returns to the calling
program. (In this way, it’s like the INKEY$ function in some versions of
BASIC.) Thus, you must continually check to see if the A-register is zero, and
do the call again if it is.
On output, put the ASCII character you want to send to the screen into the
E-register. The function will print it, without checking to see if it is a control
character.
88
Getting in Deeper
A Short Example
Here’s a very short D D T program demonstrating how the calls are made to
both the input and output functions of Direct Console I/O . All this program
does is echo on the screen whatever is typed on the keyboard.
-a100
0100 mvi c ,6 Get keyboard character;
0102 mvi e ,ff ff indicates input.
0104 ca LI 5
0107 o ra a Is A-register = 0?
0108 jz 100 Yes, go try again.
010B mvi c ,6 Got character, so print it;
010D mov e ,a put it in E-register.
010E ca L L 5
0111 jmp 100 Go wait for next character.
Save the program as “test8.ddt” and then try it out. Notice how none of
the editing control keys has any effect. Also, the backspace doesn’t work.
And, most inconvenient of all, there is no way to stop the program since
control-c is inoperative. If you needed to use Direct Console I/O , you’d have
to build all these features into your program.
Password Program
Have you ever used an automated bank teller that required you to type in
an account number? Or a time-sharing computer that requested a password
before giving you access to the system? In both these cases, what you type in
is often not echoed to the screen. This is a security precaution. It keeps any
one who happens to look over your shoulder from learning your number or
password. Our next example is a short program that shows how the Direct
Console I/O system call might be used to implement such a function.
Type in the following program and save it as “test9.ddt:”
-a100
0100 Lxi h,200 Put buffer address in HL;
0103 push h save it on stack.
0104 mvi c ,6 Set up Direct Console I/O;
0106 mvi e ,ff specify “input.”
0108 ca L L 5
010B ora a Anything typed yet?
010C jz 0104 N ot yet.
Soul of CP/M®
This program lets you type in a string of characters, without echoing them
to the keyboard, just as a real “password” program would do. To terminate
the input string, you type a dollar sign ($). Then, to ensure that everything is
working the way we expect, the program prints out the complete phrase. (It
wouldn’t do this, of course, in a real password application.)
You may have noticed something new going on in this program. It is the
letter “m” in the “mov m ,a” instruction at location 0110. W hat is this? We’ve
never mentioned an “ M” register before. Well, as it turns out, there isn’t any
such register. “ M” is nothing more than a handy abbreviation for the memory
address pointed to by the HL-register. This usage is possible because the
designers of the 8080 gave the HL register the useful ability to indirectly
address a memory location. That is, other 8-bit instructions, such as MVI and
MOV, can “pretend” there is an “M ” register, but what they really do (when
we write an “m” following the instruction) is to first look at HL to get the
address it holds and then operate on the memory location pointed to by that
address, as if the memory location were a register. This is a useful concept
when we want to store a series of data items in sequential memory locations,
as we do here with the characters that are read in using the Direct Console
I/O routine. This is illustrated by the diagram shown in Fig. 3-6.
Here’s how it works. We start off by putting the address of the first mem
ory location where we want to store our series of characters—in the HL regis
ter. In this case, we put 200 into HL (line 100). Now that we’ve done this, we
know that every time we refer to the “M ” register, we’re really referring to
memory location 200 (until we change the contents of HL). So—the first time
it is executed, anyway—the “mov m,a” instruction in line 110 has the effect of
taking the character in the A-register and putting it in location 200. But we
don’t want to put all the characters in location 200—we want the second char-
90
Getting in Deeper
HL-register
A-register 010E
010F
0110 -M O V M,A
77
A 0111
1
/
llllllllllllllllllll
llllllllllllllllllll
0204
HL-register
( .............-
0203
A/ 010D Section of memory
containing program.
A-register 010E
010F
0110 ■ -M O V M,A
0111
llllllllllllllllll
llllllllllllllllll
0204
91
Soul of CP/M®
acter to go in 201, the third in 202, and so on. We accomplish this by simply
incrementing the HL register-pair each time we put something in it. That
way, the next time we refer to “m ” in an instruction, we’ll really be talking
about location 201, or 202, or however far we’ve gotten.
BC-register
/ —
BFFF
BC-register
( — ■cooo 7/ \
Examples:
inx b
inx h
stack (line 10F), we store the character in the buffer (line 110), and we incre
ment the HL-register (line 111) so it will point to the next sequential location
in the buffer when we next call it.
To find out if the user has finished his message, we then check, with the
“cpi 24” instruction, to see if the character typed was a dollar sign. If not, we
jum p up to 103, save HL again, and wait for a new character. If it was a
dollar sign, we go on and print the contents of the buffer, starting at location
200, using our old friend the Print String system call. Since we required that
the message be terminated with a dollar sign on input, we know that there
will be one at the end of the buffer to end the message for Print String.
Finally, we return to DDT.
This system call lets you send a character to the LIST device, which is
usually the printer, in the same way that Console O utput lets you send a
character to the console screen. Of course, there are some differences in the
way the printer operates, as compared with a video screen, that must be
taken into account.
First, the usual situation is that a printer will absorb a certain number of
characters, such as one line, without doing anything; at least, anything you can
see. The characters sent are stored in an internal buffer in the printer, until
either (1) the printer’s line length is exceeded, or (2) a carriage return or other
terminating character is sent. At this point, the printer will print the entire line
of characters. So, if your program wants to ensure that what it has sent is what
gets printed out, it must send a carriage return as the last character.
Some printers automatically supply a linefeed when they receive a car-
riage-return character, others don’t. If yours doesn’t, then you need to send
one following the linefeed, to keep the printer from overprinting the previous
line.
93
Soul of CP/M®
Second, keep in mind that some characters look different on the screen
than they do on the printer. Control characters, for example, may appear as
characters preceded by a caret (A) on the screen but, instead, cause strange
nonprinting actions to the printer, such as changing the printing pitch.
This program will accept a line of input from the keyboard using the Read
Console Buffer system call and then will output the string to the printer using
List Output.
-a100
0100 mvi a , 50 Get input string. Max line length
0102 sta 01 f e into buffer.
0105 mvi c,a Read Console Buffer.
0107 Lxi d,1fe Buffer address.
010A ca L L 5
010D Lxi h , 200 Set up HL and B. HL is buffer address.
0110 Lda 01 f f Number of characters typed.
0113 mov b , a Goes in B.
0114 mvi c,5 Send character to printer.
0116 mov e,m Get character from buffer.
0117 i nx h Increment pointer.
0118 push h save H.
0119 push b save B.
011A ca L L 5 Do it!
0 1 1D pop b restore B.
011 E pop h restore H.
011 F dcr b Check if done. Decrement count.
0120 jn z 114 Not done. Go print next character.
0123 mvi c,5 Done. Print linefeed.
0125 mvi e,a
0127 ca L L 5
012A mvi c , 5 Print carriage return.
012C mvi e , d
012E ca L L 5
0131 rst 7 Back to DDT.
in the buffer starting at 200 hex, so this is the address we put in the HL register-
pair, which is the pointer to the current character.
The program loops between 114 and 120, getting a character from the
buffer (line 116), incrementing the pointer to the next character (line 117),
sending the character (line 11A, set up in line 114), and decrementing the
count (line Ilf) to see if all the characters have been sent.
Notice in the preceding printer program how we need to save both the HL
and the B registers on the stack to keep them from being destroyed by the
Read Console Buffer, which we call in line 11 A. The im portant thing to
notice here is that the order in which we save the registers is the opposite o f the
order in which we restore them. This is because the stack is “last in first out,”
so that the number we put on first will be “pushed down” into the second
position from the top when we put the next number on.
It’s easy to get confused when using the stack and pop things off into the
wrong registers. This is a relatively simple example, but when the stack is
used extensively in a program, it’s im portant to pay close attention to its use.
Program bugs involving the stack can cause more bizarre results than usual,
and they always seem to be particularly hard to track down.
READER INPUT
96
Getting in Deeper
/ / 0100
Section of memory
A-register
A 0101
containing program.
A 0102
3A
/
0103 LDA 2011
11
/ 0104
20
/
A
///////////////////
///////////////////
/ 2010 Section of memory
where constant
/ 2011
is stored.
FE
/ 2012
J
After LDA 2011 is Executed:
0101
A-register
0102
0104
iiiiiiiiiiiiin iii
iiiiiiiiiiiiiiini
Section of memory
2010 where constant
is stored.
2011
2012
97
Soul of CP/M®
This system call, and the one following, apply to devices that are no longer
found in a typical C P/M system: a paper-tape reader and a paper-tape punch.
In the old days of computing, paper tape was an important medium for storing
programs and data. Paper tape came in long rolls that were about an inch wide,
and each character was punched into the tape as a group of 7 hole-positions.
Each position in the 7-hole group could be either punched (indicating a 1) or
not punched (indicating a 0). Many tape readers and, of course, all tape-punch
devices, were mechanical and, therefore, not too reliable or fast. The introduc
tion of magnetic storage media, such as magnetic tapes and floppy disks, caused
such a dramatic change in the reliability and pleasure of using mini- and
microcomputers that it probably can’t be appreciated by anyone who has not
struggled endlessly with fiendishly tangled rolls of paper tape.
In any case, should you be unfortunate enough to be using a paper-tape
device, this system call will operate on the paper-tape reader in exactly the
same way that the Console Input operates on the console device.
PUNCH OUTPUT
98
Getting in Deeper
This system call, and the following one (Set I/O Byte), are not often used
in the operation of a typical C P /M system. However, they can be very useful
when nonstandard devices, such as a modem, are attached to the system.
W hat these calls do is make possible the assignment of different physical I/O
devices to the logical devices that the program thinks it is talking to.
What do we mean by “logical” and “physical” devices? “ Physical” simply
means the actual device itself, such as the keyboard, video screen, or printer.
Now it’s a strange and amazing fact that in CP/ M, a systems call to, say, the
keyboard (such as Console Output) doesn’t necessarily have to go to the key
board. By setting a group of four software switches, you can actually cause
the character that you sent to the keyboard (with a Console O utput call) to
end up at the printer, or you can cause what you sent to the printer to end up
on the console device. The actual device that receives the character is called
the “physical” device, whereas, the device that the program thinks it is send
ing the character to is called the “logical” device.
All this is explained in general books on C P /M (such as CP/ M® Bible, by
John Angermeyer and Mitchell Waite) in the description of the STAT func
tion. STAT can be used to do all the things the GET I/O Byte and Set I/O
Byte system calls do, but they must be done by the user from the keyboard.
W hat Get I/O BYTE and Set I/O BYTE do is let your program change the
device assignments without human intervention.
The byte referred to as the “I/O Byte” in these system calls has a memory
location in a typical C P /M system of 0003 hex. The byte is broken up into
four fields, each of which represents a logical I/O device. It is arranged as
illustrated in Fig. 3-10.
Each of the four logical devices is assigned two bits: the console gets bits 0
and 1, the list device gets bits 6 and 7, and so on. Each of these 2-bit fields
can represent four numbers (00 = 0, 01 = 1, 10 = 2, 11 = 3). Each of the result
ing four numbers is assigned to a physical device, according to the following
list:
99
Soul of CP/M®
The 3-letter mnemonics following each device are those used in the STAT
function. N ote that many of the devices listed are no longer used in a typical
C P /M system. Usually the CONSOLE is assigned to the “console printer
device” which C P /M thinks of as the teletypewriter (TTY). Although your
console uses a crt (cathode-ray tube), this device name (CRT:) is not usually
used for the console.
How does all this look on a typical system? Bring up D DT and type -d0,f.
This will cause the contents of the first 16 bytes of memory to be displayed.
Look at the byte in location 3. It will typically have a value like 94 (hex).
Let’s decode this to see what’s happening. Write down 94 in binary, like this:
10010100
10 ,01 ,01,00
The first two bits, 10, show that the LIST function has been assigned to the
line-printer device, as can be seen using the preceding list. The next two bits,
01, show that the PUNCH device has been assigned to the high-speed punch.
The next two bits, 01, show that the READER device has been assigned to
the high-speed reader, and the last two bits, 00, show that the CONSOLE
device has been assigned to the console printer device.
That’s how a human being can read the IOBYTE. How about a computer
program? T hat’s where the Get I/O Byte system call comes in. Try typing
this program in using DDT:
-a100
0100 mvi c,7 Call Get I/O Byte.
0102 ca ll 5
0105 mvi b,4 Set count of 4 in B-register.
0107 r Lc Rotate A-register.
0108 r Lc 2 bits left.
0109 mov c,a Save result in C-register.
010A ani 3 Mask off all but lower 2 bits.
010C adi 30 Add ASCII value of 0.
010E mov e ,a Store result in E-register for printing.
01 OF push b Save BC register.
0110 mvi c,2 Call Console Out.
0112 ca L I 5
0115 pop b Get BC back.
0116 mov a,c Put number back in A-register.
0117 dcr b Done 4 digits yet?
0118 jnz 107 No.
011B rst 7 Yes, back to DDT.
There are 8 bits in the A-register. Think of them as being 8 chairs placed in
a line. Two kinds of people can sit in the chairs: zeros and ones. The chairs
are numbered from 7 on the left down to 0 on the right. When the RLC
instruction is executed, everyone stands up and moves over to the chair on
his left. The person on the leftmost chair (number 7) has no chair to his left,
so he runs all the way around to the right and sits on the rightmost chair
(number 0). This is what “rotate” means in computer instructions; the bits
rotate around the A-register.
Before E xecuting RLC:
carry flag 5 4 3 2 1 0
1 0 0 0 1 0 1
z
A\
\J VJ’VJ \J \jU’vJ
(The arrow s show where the b its w ill go.)
carry flag
/ 7i / / / / / / / A
0 0 0 1 0 1 1 1
> >
Fig. 3-11. The RLC instruction.
102
Getting in Deeper
0 A ND 0 = 0
0 A ND 1 = 0
1 A ND 0 = 0
1 A ND 1 = 1
0 1 1 0 10 0 1
10 1 1 0 0 10
0 0 10 0 0 0 0
103
Soul of CP/M®
In our program, we want to get rid of all the bits in the A-register except
the two on the right (positions 1 and 0). So we “AND Immediate” a 3, which
is the number with the two rightmost bits set: 00000011. All the bits in the A-
register that match up with a 0 will be set to 0, and all the bits that match up
with a 1 will be preserved—that is, set to 1 if they are a 1 already, and cleared
to 0 if they are a 0.
This instruction is similar to the preceding ANI instruction, except that the
contents of the A-register are added to the number following the instruction.
This is a simple arithmetic addition of two hexadecimal numbers. If there is a
carry (that is, if the the resulting sum is greater than FF hex, 255 decimal),
the carry flag is set. The other flags are also set. In particular, if the result of
the addition is zero, the zero flag will be set; otherwise, it will be cleared.
Section of memory where
the program is located.
00FF
0100
ADD 15
0101
0102
Memory
address.
Examples:
adi 30
adi 7f
104
Getting in Deeper
-a100
0100 mvi c , 8 Set I/O Byte system call.
0102 mvi e , 1 4 New IOBYTE makes LIST the Console.
0104 c a ll 5
0107 rst 7 Back to DDT.
105
Soul of CP/M®
This program simply changes the IOBYTE from the 94 (hex) that we found
in the last section to 14 (hex). W hat does this do? Hex 94 is 10010100, while
hex 14 is 00010100. Thus, we changed the leftmost two bits from 10 to 00,
which (looking back at the diagram of the IOBYTE and the list of device
assignments in the last section) means that we’ve changed the destination
device of the LIST device from the lineprinter to the “console printer device”
(TTY:). How do we know that’s what’s happened? Read on!
Turn on the printer with a control-p. Now type something. It doesn’t m at
ter what it is, it will get printed twice on the screen! Why is that? It gets sent
once because the screen is simply echoing the keyboard as it normally does.
It gets printed the second time because we’ve turned on the printer echo with
a control-p, but the characters which would normally go to the printer have
been re-routed to the console device because we put a 0 in the LIST field of
the IOBYTE, instead of a 2.
106
CHAPTER 4
107
Soul of CP/M®
A true assembler such as ASM, on the other hand, doesn’t convert any of
the instructions into machine language until the entire program has been
typed in. This makes modifications to the program much easier and also per
mits the use of various other wonderful features, such as symbolic labels to
refer to memory addresses rather than hex numbers.
In this chapter, we’ll explain how an assembler works, show you how to
use the C P /M assembler ASM, and cover the use of the LOAD program,
which converts the output of ASM into a form that can be executed directly
from C P/M . We’ll start off by assemblying “TEST1” (one of the first pro
grams from Chapter 2) and then move along to a routine (“ D ECIBIN ”) that
accepts decimal numbers from the keyboard and converts them into binary
for the computer to read. This routine makes up part of our next program,
“DECIHEX,” which is used to convert decimal numbers typed in at the key
board into hex numbers on the screen. DECIHEX is a useful utility program,
as well as providing a testing ground for the use of the assembler. Finally
we’ll describe how to simplify the assemblying process through the use of the
C P /M utility, SUBMIT.
-ddt t e s t l. d d t
-1 1 0 0
0100 MVI C,02
0102 MVI E,48
0104 CALL 0005
0107 MVI C,02
0109 MVI E,49
01 OB CALL 0005
01OE JMP 0100
Now, dump the contents of the memory locations occupied by the pro
gram, using “d” :
- d 1 0 0 , 1 10
— mvi c,2”
------- “mvi c,2”
I------“mvi e,48” | ----- “call 5
j—.“call 5”
Notice how the mvi instructions have different codes depending on what
register they’re addressing: 0E for the C-register and IE for the E-register. In
general, instructions that include an 8-bit byte (like the 02 in “mvi c,2”) are
two-bytes long, and those that include a 16-bit address, like the “call” and
“jm p ” instructions, are three-bytes long.
The hexadecimal numbers revealed by the “d” function are representations
of the binary numbers that the computer actually operates on when it is exe
cuting a program. The microassembler in D D T has taken symbolic instruc
tions, such as “mvi c,2” , and converted them into these binary numbers
(which DDT translates into hexadecimal, so we can read them more easily).
This is essentially what all assemblers do, but true assemblers do it much
more elegantly than the D D T microassembler.
cess is quite different and is somewhat more involved. You first type your
symbolic input into a text file, using a word-processing program, ju st as if
you were writing a letter. This text file, which is called an ASM file (and must
have a file extention of ASM), is then stored on the disk, like any other text
file. You then call up the assembler program ASM, which performs the actual
assembly process: that is, figuring out what binary numbers should stand for
each symbolic instruction. ASM generates two files: (1) the HEX file, which
consists of the binary numbers, but in a special format which is not yet
directly executable, and (2) the PRN (for “print”) file, which is the same as
the original ASM file, but with the hexadecimal addresses and instructions
included alongside the original symbolic instructions so that you can see how
they all fit together.
Finally, the LOAD program is used on the HEX file to generate a COM
file, which can be executed directly by C P/M .
Alternatively, the HEX file can be executed, inspected, and modified
directly from DDT. This is useful if the program has a bug in it (a topic we’ll
discuss later), or if access to the 8080 registers is necessary while the program
is being used, as is the case in the “ D ECIBIN ” routine that will be described
later in this chapter.
The diagram in Fig. 4-1 shows the relationship of these files and programs.
ASM File
Symbolic Instructions
mvi c,2
mvi e,48
call 5
etc.
ASM Program
COM File
Binary numbers,
directly executable.
There are several differences in the way that you type in programs using
DDT and the way you type them using ASM.
org 100h
mvi c,2
mvi e ,48h
ca L L 5
mvi c,2
mvi e,49h
ca L L 5
jmp start
That’s not so different from the D D T version, is it? We’ve started off with
the ORG statement, and we’ve put an “h” after all the hex numbers (except
those that are the same in both hex and decimal, like 2 and 5). You’ve proba
bly noticed one other difference: the word “ start” that precedes the first mvi
c,2 instruction and, also, follows the jm p instruction. W hat’s this all about?
Symbolic Labels
One of the really nice things about using an assembler is that it relieves you
of the responsibility of figuring out what memory address a particular
instruction or data item occupies. In DDT, it was necessary, when we wrote
the jm p instruction at the end of the program, to look up at the place at the
beginning of the program where we wanted to jum p to, and then write that
same number following the “jm p ” ; in this case, “jm p 100.” Of course, that’s
not a problem in this particular example, but in longer programs, especially
those where you want to jum p down (that is to an instruction you haven’t yet
112
Using the Assembler
written), the use of symbolic labels is invaluable. Also, as you will see in the
next few programs, the labels given to subroutines can visually help to break
the program into separate and more understandable sections.
You can use a label to refer to any particular address. A label can be any
group of letters and numbers, but it must start with a letter, and it should not
be longer than 8 characters or it will cause formatting problems when you try
to type it in, and when ASM generates the PRN file. (Although, actually,
ASM can accept up to 16 characters.) This label is placed in the “label field”
in the program: that is, the first column of the listing. The instruction itself
(as we learned earlier) then goes in the next column, which is called the
“operation field.” The third column is called the “operand field” since it con
tains the thing that the instruction is going to operate on- th e operand. This
operand can contain either register names and constants, as is the case with
the mvi instructions, or it can be a symbolic label, as in the case of the jm p
instruction. There is another column, the “comment” field, which we’ll
explore later. The fields are separated from each other either by tabs or by
any number of spaces, but using the tab key makes for neater listings.
Once you’ve written the program and checked it over for errors, save it on
your disk using the appropriate command from your word-processor pro
gram. D on’t forget that it must have an ASM file extension. If it doesn’t, the
ASM program won’t function correctly.
Now, return to C P /M and call up ASM, followed by the name of the pro
gram you want to assemble. But don’t type any file extension following the
name o f your program. Just type the program name and a carriage return—
don’t even type a period. This is because ASM interprets the letters, which
appear in the place usually occupied by the file extension, in an entirely dif
ferent way. For the time being, we won’t worry about this, so leave the letters
out.
113
Soul of CP/M®
ASM introduces itself with a sign-on message that gives its version
number, and then proceeds to assemble the program. When it is done, it
prints the address following the last address used in the program; 0111 in this
case. The “USE FACTOR” has to do with the am ount of space used by
symbolic labels. There is only a certain am ount of space for storing these
labels in ASM; if you use too many labels, you’re in trouble. The USE FAC
TOR tells you the percentage of available space used in your program. The
programs used in this book seldom have a USE FACTOR of more than 3%.
In the present case, with only one label in the program (“ start”), the USE
FACTOR doesn’t even get up to 1%.
If you have made any errors in typing your program, ASM will print them
out during the assembly process. The error messages are single letters incor
porated into the offending program line and can be rather obscure; usually, a
glance at the line will show you where you went wrong.
Finally ASM tells you END O F ASSEMBLY, and returns you to C P/M .
If you now use “dir”, you will see that two new files have been generated:
TEST1.HEX and TEST1.PRN. To see what ASM has been up to, display the
PRN file:
A>type t e s t l . p r n
You’ve got to admit that’s pretty slick. Everything is all neatly printed out,
you can see at a glance what hex values go with what symbolic instructions,
and the program has even figured out that “jm p start” means “jm p 100.”
About the only inconvenience is having to remember to reverse the order of
114
Using the Assembler
the address bytes; thus, at line 104, the CD0500 means “call 0005,” not “call
0500.”
You can’t actually execute (run) the HEX file from C P/M : it’s not in quite
the right format. You’ll need to put the program into the correct format for
execution, using the LOAD program. To do this, simply type “load” followed
by the name of the program, againwithout a file extension. LOAD assumes
that it will be operating on a file with an extension of HEX, so typing the
extension can cause trouble. LOAD also assumes it will be operating on a
program that starts at 100 hex, so don’t forget the “org lOOh” directive at the
start of your program.
The program will start and the screen will fill up with the word “H I”, just
as it did under DDT. Another pat on the back is in order. You’ve just assem
bled and executed your first program using ASM. You are now a real
programmer! All that stands between you and fame and fortune is a little
more practice, some of which we’re about to give you.
Some nomenclature might be in order here. The ASM file that you type in
is often called the “source file.” The COM file that you actually execute is
called the “object file.” Individual lines of the source file are called “source
code,” and individual lines of the object file are called “object code.” These
terms won’t make you a better programmer, but they’ll make you sound like a
115
Soul of CP/M®
real pro if you drop them into your conversation from time to time. (As in,
“A head crash trashed my object file, so I had to reassemble.”)
Before we describe how our DECIBIN routine works, you should make
sure that you understand the distinction between binary and hexadecimal
numbers. If necessary, read Appendix A. Essentially, binary is the way that
the computer stores numbers, while hexadecimal is a convenient way for
human beings to talk about binary numbers. A computer program can take
the binary numbers stored in the computer and print them out on the screen
in hexadecimal notation. (It could also print them out in binary notation, but
that wouldn’t be so easy for a human to read.)
Frequently, when we talk about numbers in the computer, the distinction
between binary and hexadecimal becomes a little fuzzy. The number in the
computer is in binary, but when we print it out so that we can see it, as we
might do with DDT, we’ll look at it as a hexadecimal number. It’s really
always a binary number, but when it reveals itself to us on our computer
screen, we see it as hexadecimal.
The DECIBIN routine that we’re about to discuss reads in a decimal
number from the keyboard and changes it to a binary number and stores it in
the HL-register. At least, it’s a binary number when it’s safely in the com
puter. If we wanted to see what the number was, we would print out the
contents of the HL-register using DDT, and the number will appear in hex
adecimal form.
The program uses a different strategy to convert from decimal to binary
than a human might. (For a human approach to decimal-to-binary conver
sion, see Appendix A on Hexadecimal Notation.) Here’s how the program
116
Using the Assembler
does it. It uses the HL-register to store the binary number. At the beginning
of the program, it sets HL to 0. Then, each time the user types in a decimal
digit, the program performs the following steps:
Thus, if the user types “432,” the program first sees the 4, and converts it
to binary (which we’ll refer to as 4 hex). The old value in HL was 0, so when
it multiplies this by 10 it’s still 0. Then, when it adds the new digit, the result
is 4 hex. Next, the user types the 3. The program converts this to 3 hex,
multiplies the old 4 hex by 10 decimal (which makes it 28 hex), and adds the
3; thus, obtaining 2B hex. If the user at this point typed a return, signaling
that he was finished typing in the number, the correct binary translation of
43 decimal would be in the HL-register: 2B hex. However, the user now types
a 2, little realizing how hard he’s making the program work.
The program converts the 2 to 2 hex and, then, proceeds to multiply the
old 2B hex by 10 decimal, which makes it 1AE hex. (2B hex is 43 decimal,
times 10 is 430 decimal, which is 1AE hex.) Adding the 2to 1AEmakes it
1B0 hex, and that’s what remains in the HL-register if the user now types a
return. Whew! All that trouble just to go from decimal to hex notation.
H ex number in
HL-register
H L
0 0 0 0
--------------- User types “4” , program multiplies 0 by 10
0 0 0 0
- ------------and adds the 4.
0 0 0 4
---------------- User types “3”, program multiplies 4 by 10
0 0 2 8
-------------- and adds the 3.
0 0 2 B
-------------- User types “2”,program multiplies 2B by 10
0 1 A E
-*------------- and adds the 2.
0 1 B 0
-------------User types “return”, 1B0 hex is 432decimal.
Soul of CP/M®
Why do it this way, rather than dividing by 4096, then by 256, and then by
16, as shown in Appendix A? Because it’s easier for the computer to multiply
by 10 than it is to divide by all those other numbers. Next question: how does
the computer multiply by 10 when there are no multiplication instructions in
the 8080? Well, the computer can easily double a number simply by adding it
to itself.
Can you figure out how to multiply a quantity by 10 simply by using the
doubling operation? Here’s how the program does it. It takes the original
number in HL and copies it into the DE-register. Then it adds HL to itself,
which doubles the number. If, for example, we started with 1, we now have 2.
The result is in the HL-register. It adds HL to HL once more, which again
doubles the number giving us a 4 in HL, with a 1 still left in the DE-register.
It adds DE to HL which gives a 5, and, finally, it adds HL to HL again which
doubles the result, giving us 10. Clever, no?
Listing 4-1 shows the DECIBIN routine. We have used the PRN file so
that it will be easy to refer to the particular program lines by their memory
addresses.
You’ll notice that the program is liberally sprinkled with comments. This is
made possible by the semicolon (;). Anything following a semicolon is a com
ment and is ignored by ASM (in the sense that it won’t try to turn it into
binary instructions). The semicolon can occur any place in a program line,
but the usual place to put it is at the beginning of a line (when you want the
entire line to be a comment) or at the beginning of the comment field (the
one on the right). It’s always a good idea when you write a program to use
even more comments than may seem necessary, since a program listing is
never as clear when you look at it some time after you’ve written it as it is
when you’re writing it. And, in assembly language, there is no penalty for
comments, as there is in interpreted languages such as BASIC (where com
ments occupy memory space when the program is running and, also, slow
down execution speed). In assembly language, the comments exist only in the
ASM and PRN files—they disappear completely when the program is ren
dered down into binary in the HEX and COM files.
Something else to notice about the listing is that we have added an “end”
statement to the end of the program. Although the ASM assembler does not
require this statement, many other assemblers do and it is good practice to
include it. The “end” statement has another use in ASM and that is to specify
where a program will start; that is, what instruction will be executed first.
118
Using the Assembler
c a l l i n g p r o g r a m - t o c a l l r o u t i n e and r e t u r n t o DDT
0100 CD0401 ca 11 d e c i b i n ; c a 11 r o u t i ne
0103 FF rst 7 ; b a c k t o DDT
f
; d e c ib in subroutine
/
0104 210000 d e c i b i n l x i h,0 ; s e t h i to 0
0107 E5 newdig push h ; s a v e h i (Con I n p u t uses i t )
0108 0E01 mvi c,1 ;g e t character
01OA CD0500 ca 11 5
01 OD E1 pop h ; r e s t o r e hi
01OE D630 sui 30h ; c o n v e r t f ro m ASCII t o b i n a r y
0110 F8 rm ; r e t u r n i f i t was < 0
0111 FEOA cpi 10d ;is i t >9 ?
0113 FO rp ; i f so, r e t u r n
W
; m u l t i p l y contents of h i by 10 ( d e c ) , t hen add new d i g i t
F
0114 E5 push h ; p u t h i i n de
0115 D1 pop d
0116 29 dad h ; a dd h i t o h i ( d o u b l e i t )
0117 29 dad h ; d o u b l e i t agai n
0118 19 dad d ; a dd de ( o r i g i n a l number) t o h i
0119 29 dad h ;double r e s u lt
011A 16 mvi d,0 ;zero in d
011B 5F mov e , a ;new d i g i t i n e
011C 19 dad d ; a dd d i g i t t o number
; r e s u l t i s now i n h i
/
01 1D C30701 jmp newdig ;go look f o r next d i g i t
0120 end
119
Soul of CP/M®
(Note that this is not the same as the first instruction in the program, nor
where the program starts in memory.)
If, for example, the last statement in your program is
end 100h
then, the first instruction to be executed will be at 100 hex. (This information
is included by ASM in the HEX file.) This is useful if you have a program
that you want to start executing in the middle. Only HEX files will be given
an arbitrary address this way; COM files are assumed to start at 100 hex.
Subroutines
We have used the “call” instruction many times before when executing a
system call to BDOS (“call 5”). As we pointed out, “call” is like a “GOSUB”
in BASIC. It can be used to call any sort of subroutine, not just BDOS. A
subroutine is simply a section of code that performs some function, that has a
beginning (which is the address one uses to “call” the subroutine), and which
ends with a “ret” instruction (which returns control to the calling program).
If you’re hazy on what CALL does, you might want to review the description
of it at the beginning of Chapter 2.
In our DECIBIN routine, note that lines 100 and 103 are actually the
“program.” All these lines do is call the DECIBIN subroutine (which starts
at 104) and, then, return to D D T with a “rst 7” instruction. Why make
DECIBIN a subroutine? Because later, we’re going to use DECIBIN in a
larger program where it will need to operate along with other subroutines
and, also, because we can simplify the coding of the program if we can use
instructions such as RM and RP, instead of RST 7.
The RP instruction will cause a return (the same as a ret instruction) if the
result of a preceding arithmetic operation has set the sign flag to plus. If the
sign flag is minus and this instruction is executed, control will simply con
tinue on to the next instruction after the RP. The RM instruction will cause a
return if the sign flag is minus; if it is plus, control will go on to the following
instruction.
The RP instruction functions in the same way, except that it causes a
return to the calling program if the sign flag is set to plus.
Examples:
rm
rp
0200
0100
/ /
0201
0101
0202
0102 CD
CALL 200 —
/ 0203
0103 00
0204
0104 02
0205
0105
0206
0106 RM F8
0207
0107
121
Soul of CP/M®
the A-register, rather than being added. Thus, “sui 2” means “subtract 2 from
the contents of the A-register.” This instruction, like other 8-bit arithmetic
instructions, sets the zero flag and, also, the sign flag.
Examples:
sui 30
sui I f
In the DECIBIN routine, the first half of the program, from locations 104
to 113, is concerned with figuring out if the character that the user has typed
in is a decimal digit or not. If it is, it’s converted to binary. If n o t-th a t is, if
the user types any character other than a decimal digit—the subroutine is
terminated and control returns to line 103 and, thus, back to DDT.
Let’s look at this in detail. The lxi h,0 instruction clears the HL register,
which we need to do at the beginning of the program to get ready to add the
digits to it. Then we save HL with a “pop h” instruction, since the Console In
system call, as we’ve learned before, trashes all the registers and we need to
remember the contents of HL. We call the Console In routine in lines 108
and 10A to get the character from the keyboard and, then, we restore HL in
10D.
Section of memory where
the program is located.
Now comes the interesting part. We have the character in the A-register. Is
it a decimal digit? To find out, and at the same time to convert it from ASCII
to binary, we subtract 30 hex in line 10E. (Recall that 30 hex = “0” in
ASCII, 31 hex = “ 1,” and so on up to 39 hex = “9.”) If, when we subtract 30
hex, the result is negative, then we know that the character typed in must
have had an ASCII value less than 30 and, therefore, it could not have been a
decimal digit. If the result is positive, then it may be a decimal number, pro
vided it isn’t too big. Since we’ve changed it to a binary number, we now
need to see if it is greater than 9, so we do a “cpi lOd” which performs a
“phantom ” subtraction of 10 decimal from the contents of the A-register,
and sets the flags accordingly. If the result is positive (or 0, which is consid
ered to be positive by the sign flag), then it must have been greater than 9,
and is therefore not a decimal digit. The “rp” instruction will, therefore,
cause a return to 103 and back to DDT.
123
Soul of CP/M®
There are not nearly so many arithmetic instructions for 16-bit quantities
in the 8080 as there are for 8-bit quantities. In fact, except for incrementing
and decrementing, which are rather limited arithmetic operations, DAD is
the only one! It was probably included in the 8080 instruction set to provide
a way to calculate addresses but it is, of course, useful for other 16-bit quanti
ties as well. Any of the 16-bit register-pairs—BC, DE, or H L —can be added to
the contents of the HL register-pair. The result is always left in HL.
As in other register-pair instructions, only the first letter of the register-
pair name is used in the operand field. Thus, “dad d” means “add the con
tents of the DE register-pair to the contents of H L” ; “dad h” means “add the
contents of HL to itself.”
Note that DAD does not set either the zero fla g or the sign flag. It does,
however, set the carry flag.
Examples:
dad d
dad b
dad h
The second part of the subroutine, from 114 to 11C, is concerned with
multiplying the contents of the HL-register by 10 and then (line 11C) adding
the new digit obtained in the first part of the program. As described earlier,
the multiplication by 10 is accomplished by adding the contents HL to itself
Before DAD B is executed:
z :
02F1
BC03
f a
BC 03
0 2 F 1
z :
BEF4 02F1
124
Using the Assembler
twice (lines 116 and 117), then (in line 118) adding the original number
(stored for that purpose in the DE-register in lines 114 and 115), and, finally,
adding HL to itself again in line 119.
Notice, in lines 114 and 115, how easy it is to copy the 16-bit number from
HL into DE. We simply do a PUSH H to write the number from HL into the
stack and, then, a POP D to read it back into the DE-register. As long as we
make sure to follow a PUSH with a POP, the stack is left unchanged.
The new digit is then added to the result in HL by placing it in the DE
register-pair and adding DE to H L in line 11C. Notice how the D-register is
first cleared and then the new digit is transferred from the A-register to the
E-register (lines 11A and 11B), since it takes two 8-bit transfers to fill up a
16-bit register. After this, we go back to get another character with the “jm p
newdig” in line 11D.
Type the program using your word-processing program, assign it the name
“decibin.asm”, and then assemble it with ASM. Look at the resulting PRN
file. It should look ju st like the one just shown. If ASM tells you there are
errors, then you’ve probably made a typo somewhere. Fix it up until you
have an error-free assembly.
Since we need to look at the HL register-pair after we execute this routine
to see if it has done its job, we’ll run the program using DDT. D D T (unlike
C P/M ) can load HEX files directly, so we’ll use the HEX file version of our
program. When you start the program, it will just sit there waiting for you to
type a decimal number. When you’ve finished typing the number and hit
return, it will return to D D T and print the asterisk and the ending address.
When it does this, D D T will overprint your original number, but that’s all
right; the program has already read it.
Now you want to examine the contents of the HL-register, so you type “x”
for eXamine, followed by “h” for the HL-register, and return. D D T will print
out the contents of HL, and then wait for you to type a number to enter into
HL; but that’s not necessary here, so hit return to get back to DDT.
A>ddt d e c i b i n . h e x
-g100 Start the program.
4096 Type decimal number (then return).
*0103 (Actually prints over above line.)
-x h Type this to look at HL.
H=1000 D D T prints out contents (in hex).
125
Soul of CP/M®
Since 1000 hex is the equivalent of 4096 decimal, you know the program is
working correctly. You can try it with other decimal numbers.
-g 1 00
10 Decimal number.
*0103
-xh
H=000A Hex equivalent.
-g100
32767 Decimal number.
*103
-xh
H=7FFF Hex equivalent.
And so forth. Notice that you can’t input a number greater than 65535,
because that’s the largest number that can be represented by 16 bits (four hex
digits).
Hold on to your hats. For the first time, we’re going to write a program
that is actually useful! By adding a routine which prints a hex number on the
screen to the DECIBIN routine already described, we’re going to create a
program that will convert a decimal number that you type in, to a hex
number, right before your very eyes! The program will be a COM file so you
can run it directly from C P/M , thus amazing your friends and teaching you
the hexadecimal numbering system at the same time. Fig. 4-6 shows how the
routines fit together.
Let’s talk about the new subroutine we’re going to add, which prints a
binary number out on the screen. We’ll call it BINIHEX. It turns out that it’s
surprisingly difficult to convert a four-digit hex number (really a 16-bit
binary number) into four individual ASCII digits. This is because two of the
digits are in one register (H) and two are in another (L), and two are on the
126
Using the Assembler
left side of their respective registers while two are on the right. We’ll make the
project somewhat simpler by always printing four digits, even if they are
leading zeros. Fig. 4-7 shows how it will look.
Digit 1, the most-significant digit, will be printed first, and so on, down to
digit 4. To print the digits on the left-hand sides of the registers (1 and 3), we
will have to shift them over to the right-hand side of the A-register so that
they can have 30 (hex) added to them to make them into an ASCII code.
(The digits on the right-hand sides, 2 and 4, don’t need to be shifted.)
Another problem is that the hex digits, A to F, need to be converted to
ASCII in a slightly different way than the digits 0 to 9. This is because the
letters A, B, C, etc., don’t immediately follow the numbers 7, 8, 9 in the
ASCII coding system. (An unfortunate situation that results from the fact
DECIHEX Program
DECIBIN Routine
Converts decimal
from keyboard
to binary in HL.
BINIHEX Routine
Converts binary in
HL to hexadecimal
on screen.
^ R etu rn to C P /M ^
H-register L-register
127
Soul of CP/M®
that ASCII was invented before hexadecimal notation became popular.) List
ing 4-2 shows the BINIHEX subroutine.
showing this subroutine by itself for clarity, even though it forms part of a
larger program (as you can see by looking ahead a few pages).
BINIHEX consists of three parts. PCHAR is a subroutine whose only
function is to print a character on the screen. It is entered with the character
in the A-register, and before it calls the Console Out routine (lines 14E and
150), it saves the HL-register because this is where the 16-bit binary number
that we’re going to print out is stored.
PR IN T 1 and PRINT2 are two different entry points for the subroutine
that prints out, in ASCII, the 4-bit hexadecimal digit that is in the A-register
when the routine is entered. If you want to print the hex digit on the left side
of the A-register, you call PRINT1; if you want to print the hex digit on the
right, you call PRINT2. This is because entering at PR IN T 1 causes four
additional instructions to be executed. The instructions are written in a some
what unusual way.
If you want to write a number of instructions on the same line, rather than
on separate lines as we have done so far, all you need to do is separate the
instructions by exclamation points. This is useful when a group of similar
instructions that perform the same task can be grouped together. In this case,
we need to rotate the A-register 4 bits to the left so that the leftmost 4 bits
will end up on the right. Since each RLC instruction rotates the contents of
the A-register 1 bit, we’ll do four instructions to move it the required 4 bits,
and we’ll separate the instructions with exclamation points for clarity. (Of
course, we could also have written each RLC instruction on a separate line—
the assembled binary program would have been ju st the same.)
So, whether the A-register must be rotated or not, when we get to line 13D
we know that the hex digit we want to print is on the right side of the A-
register. Our next step is to “mask off” the upper 4 bits, which are meaning
less. We do this with the “ani Ofh” instruction. The number “Of hex” is
00001111 in binary, so when we A N D it with our 8-bit number, only the
rightmost 4 bits will be left. Then, we add 30 (hex), to change the digit from
binary into ASCII (since 30 hex is the ASCII code for 0).
Now we have to figure out if the resulting 4-bit ASCII digit is in the range
from 0 to 9 or in the range from A to F. To determine this, we do a “compare
immediate” with 3ah, since 3a (hex) is the ASCII code for 10. If the result is
minus, we know that the number is less than 10 (decimal), so we jum p over
129
Soul of CP/M®
the next instruction and go to “notbig.” On the other hand, if the result is
plus, we know that our ASCII digit is more than 9, so it must be in the range
from A (hex) to F (hex). If you look at the table of ASCII values in Appendix
D, you’ll see that “A” is 41 hex, while “ 9” is 39 hex. If “A ” immediately
followed “9” in the ASCII table, it would be at 3A hex, so there are seven
characters between where “A” actually is in the ASCII table and where it
should be. To make up for this difference we need to add an extra “7” to any
ASCII character representing the hex numbers from A to F. We do this in
line 146. Finally, in line 148, we call the PCHAR subroutine, already
described, to print the ASCII digit on the screen.
Now, we can look at the main part of the BINIHEX routine, from line 128
to line 138. These lines are responsible for making sure that the four digits
that get printed come from the right place. The first two lines print the left
side of the H-register. This is done by putting the H-register into the A-regis-
ter and then calling PR IN T 1 which, as we have seen, prints the left-hand
digit (4 bits) of whatever is in the A-register. The next two lines print the
right side of the H-register. Finally, the last four lines repeat the process with
the L-register, and then returns to the main program.
Now we can put together the DECIBIN and BINIHEX routines into a
complete program. The combined DECIHEX program is given in Listing
4-3.
; ma i n p r o g r a m - l i n k s s u b r o u t i n e s t o g e t h e r
130
Using the Assembler
; m u l t i p l y c o n t e n t s o f h i by 10 ( d e c ) , t he n add new d i g i t
011C E5 push h ; p u t h i i n de
011 D D1 pop d
011E 29 dad h ; ad d h i t o h i ( d o u b l e i t )
0 11 F 29 dad h ;d o u b le i t again
0120 19 dad d ; ad d de ( o r i g i n a l number) t o h i
0121 29 dad h ;double r e s u lt
0122 50 mov d,0 ;zero in d
0123 5F mov e ,a ;new d i g i t i n e
0124 19 dad d ; ad d d i g i t t o number
r e s u l t i s now i n h i
b i n i h ex- p ro gr a m t o p r i n t b i n a r y number i n h i
o u t on s c re e n i n hex
0128 7C b i n i h e x mov a , h ;p u t h in a ( f i r s t d ig i t )
0129 CD3901 c a llp rin tl ; p r in t le ft-h a n d d i g i t
012C 7C mov a , h ; p u t h i n a ( second d i g i t )
012D CD3D01 c a llp rin t2 ; p r in t right-hand d i g i t
131
Soul of CP/M®
; s u b r o u t i n e t o p r i n t c h a r a c t e r i n a - r e g o u t on s cr ee n
f
014C E5 p c ha r push h ; s a v e h i ( c o n o u t uses i t )
014D 5F mov e,a ; p r i n t hex d i g i t
014E 0E02 mvi c,conout
0150 CD0500 c a l l bdos
0153 E1 pop h ; g e t h i back
0154 C9 ret
0155 end
instance, that to call the Console In system call, we need to do an “ mvi c ,l”
instruction. T hat’s fine, but the num ber “ 1” is not really very descriptive of
what the instruction is trying to do. It’s much clearer to be able to use an
illustrative sort of name in the instruction, like “ mvi c,conin” , which is
exactly what we do in line 110. But how will the assembler program know
what we mean when it sees “conin” used in this way? It won’t, unless we tell
it, and th at’s what the EQU directive is for. Using EQU, we can tell the
assembler that every time it sees a certain name, it is to substitute the
num ber given in the equ statement.
As another example, in line 103, we say “mvi a,If’. (These are the lower
case letters “LF,” for “Linefeed.”) W hat we’re doing here is loading the
ASCII code for a linefeed into the A-register, where this value has already
been defined in the statement “If equ Oah” at the beginning of the program.
We could have said “mvi a,Oah” in line 103, but unless we happened to
remember that 0a hex is the hex code for linefeed, we wouldn’t have known
what the instruction meant.
When to use an EQU directive and when to simply use a regular hex or
decimal number in an instruction is largely a matter of style. M any program
mers follow the convention that instructions that refer to memory locations
outside o f the program, such as the entry point to BDOS at 5 hex, must be
given a symbolic name with an EQU directive. This convention makes it eas
ier to go back and change the program if, for example, you wanted it to work
on a C P /M system where BDOS started at a location other than 5 hex. O th
erwise, EQU is used when it will make the program easier to read. By con
vention, EQU statements are all grouped at the beginning of a program
where they are easier to find.
Since we already know all about the D ECIBIN and BINIHEX subrou
tines in this program, there isn’t too much more to say about the program.
The first four lines, from 100 to 10B, link the subroutines together. First, we
call DECIBIN, to get the decimal num ber that the user will type in on the
keyboard. The num ber is placed in the HL-register where it will remain
when we return to the main program in line 103. Then, we print a linefeed,
so that when we print the hex number, it won’t print on top of the decimal
num ber that was ju st typed in. To print the linefeed, we make use of the
PCH AR subroutine that is already a part of the BINIHEX routine. We put
the ASCII code for a linefeed in the A-register and call PCHAR. To print
the hex version of the number, we then call BINIHEX, in line 108, and our
job is done!
133
Soul of CP/M®
Now, since we want to run this program directly from C P /M (rather than
DDT), we’ll convert the HEX file to a COM file with the LOAD program:
A>load d e c i h e x
FIRST ADDRESS 0100
LAST ADDRESS 0154
BYTES READ 0055
RECORDS WRITTEN 01
And now, lo and behold, we can actually execute the program and see if it
works. Type the program name (no extension needed to execute a COM file),
and when the program is loaded and waiting, type any decimal number
between 0 and 65535:
A>deci hex
65535
FFFF
A>
if your word processor doesn’t create one.) Of these, you only need to store
the ASM and COM files, since you can generate the others from the ASM file
whenever you want to. W ouldn’t it be nice if there were a way to start with
the ASM file and end up with just the ASM and COM files without having to
go through all the steps of creating and, then, erasing the intermediate files?
The C P /M utility program SUBMIT permits us to do ju st that.
If you haven’t used SUBMIT before, here’s a brief description of how it
works. The idea is to take a string of C P /M commands (the kind you type
following the A > prompt) and put them together in a text file. Then, if you
run the SUBMIT program, these commands will be executed one after the
other.
Let’s try it out. Use your word processor to create the following file, and
give it the name “quick.sub” :
Type this in. r--------These are just comments; don’t type them in.
Now, let’s assume that you have the following files on your disk in drive A:
A>submi t q u i c k d e c i h e x
My goodness, what a clicking and whirring of the disk drive there is now!
It’s hard to believe that one little phrase could cause so much activity. C P /M
is erasing the “decihex.bak” file, assemblying the “decihex.asm” file with
ASM, erasing the “decihex.prn” file, LOADing the “decihex.hex” file and
then erasing it, and, finally, it executes the “decihex.com” file.
How does all this happen? SUBMIT looks first for the file with an exten
sion of SUB whose name was typed in following SUBMIT: in this case,
135
Soul of CP/M®
QUICK. It then proceeds to “execute” the lines of this file as if they were a
program written in “C P /M language,” which, in a way, they are. When SUB
M IT sees the “era $ l.bak” statement, for example, it looks for the file name
that was typed in following the name of the submit file QUICK; in this case,
DECIHEX. Then, wherever there is a $ 1, SUBMIT will fill in the name of the
file: DECIHEX. So, the first line is translated into “era decihex.bak” . The
second line is translated into “asm decihex” , and so on. (Actually, more than
one file name can be dealt with in this way, by using $2, $3, and so on, but we
don’t need to get into that here.)
Amusing, no? And also a great time saver. You can customize this little
Q U IC K file to suit your needs. For instance, if you want to retain the BAK
file for safety’s sake, leave out “era $ l.bak” . And, if you don’t want to exe
cute the program right away, leave out the last line: “$1” .
GRADUATION TIME
You now know almost as much about 8080 assembly language as anyone.
Oh, sure, there are some more instructions in the 8080 repertoire, but the
ones you’ve already learned are used in 95% of all programming situations. In
fact, you could probably write any program you wanted to by just using only
the instructions you’ve learned so far. We’ll pick up a few more along the
way, but they won’t be too different from what you know already.
Also, you’ve learned to use the sophisticated (at least when compared with
DDT) ASM assembler to assemble your program. Now you can write pro
grams of almost unlimited length (subject to the limitations of your com
puter’s memory, of course) with the assurance that you’ll be able to turn them
into object code. So, how about a round of champagne? You’ve graduated
from 8080 school!
136
CHAPTER 5
137
Soul of CP/M®
Learning how to use these disk system calls is the purpose of this chapter.
You’ll find out about the “mailbox” system that C P /M programs use to tell
C P /M what files to read or write, and you’ll explore several short system
calls which will be all you need to have to read any file into your program. By
the end of this chapter, you’ll be able to reproduce the C P /M “type” com
mand and also write a program that counts the number of lines in a text file.
(We’ll save writing to the disk until the next chapter.)
If this sounds complicated, relax. Thanks to the transportability features of
C P/M , learning how to input and output inform ation to the disk is scarcely
more complicated than it is for the console. Well, maybe a little more compli
cated, but it’ll be fun. Trust us!
One of the truly wonderful things about C P /M is that you can write pro
grams in 8080 assembly language, which will do almost anything you want
with the disk drives, without knowing anything about most o f the terms, such as
those listed in the heading of this section. All you really need to understand
138
Disk System Calls
are records and files. We won’t talk (too much) about the other terms until
Chapter 7, when we get to such mysterious topics as the file directory and
saving erased files.
Files
Records
TALKING TO BDOS
When it is reading or writing information to the disk, the Basic Disk Oper
ating System (BDOS) uses two areas of memory to communicate with the
139
Soul of CP/M®
program calling it. These are the DMA buffer and the File Control Block
(FCB). Understanding the DMA and the FCB and how they’re used is the
key to understanding the C P /M disk system calls.
Fig. 5-1 shows where the DMA buffer and the FCB are located in memory.
They’re both in the “zero page,” the portion of memory from 0000 hex to
00FF hex.
The DM A Buffer
When you want to read a file from the disk into memory, the process goes
like this. You set aside a place in memory that is 128 bytes long. This space is
called the “ DMA buffer.” (DMA stands for “ Direct Memory Access” and is
actually a term better used with large mainframe computers that can transfer
a block of data without the intervention of the a program —but that’s another
story. The name got started and here it is.) The DMA buffer is usually
located at memory address 80 (hex), which is called the “ DMA Address,”
although you can change it to any address you want, as we will soon see.
Once the DMA buffer is set up, you read the first record of the file into
this space. When your program has done whatever it’s going to do with this
record (printing it out on the screen, doing arithmetic on data in the record,
or whatever), it then reads in the next 128-byte record, processes it, and so
o n -u n til the end of the file is reached.
How can your program tell C P /M what file to read? We need a way to
pass file names from your program to C P/M , so that BDOS can take care of
all the tedious details of looking for the file on the disk, finding the record we
want and reading it off the disk, and putting it in memory in the DMA
buffer. To pass these file names, we set up a sort of “mailbox” in memory,
lllllllllllllll
II I I I III l l l l
/
TPA
/
DMA
Buffer
/
FCB
/
/
Fig. 5-1. The memory location of the DMA buffer and the FCB.
140
Disk System Calls
which we call the FCB, for File Control Block. This is simply a section of
memory, 36 (decimal) bytes long, where we can put the file name. C P /M also
uses the FCB to store information about where the file is located and some
other things, but we don’t need to worry about that now. The FCB is usually
located at memory address 5C (hex), just below the DM A address.
The following listing shows the various sections of the file control block
(FCB). For the time being, we are only interested in the place where the file
name and extension go. (The mnemonics shown in parentheses are the desig
nations given the various bits by Digital Research.)
27 77 00
28 78 00
29 79 00 ►Allocation units ( d ll to dl5).
30 7A 00
31 7B 00
32 1C 01 Current record (cr).
33 7D 00
34 7E 00 ^•Random record number (rO, rl, r2).
35 7F 00
Now that you know about the DM A buffer and the FCB, we’re ready to
plunge on into our first system call.
OPEN FILE
W hat does it mean to “open” a file? Before a program can do any reading
or writing to a file, BDOS has to figure out where the file is on the disk. It is
alerted to do this with the “Open File” system call. When this call is exe
cuted, BDOS finds the addresses of the various parts of the file (they’re called
“allocation units”) and records them in the FCB for later reference. Since
there is only one FCB, there can only be one file open at any one time. (Actu
ally, there can be several FCBs operating at once, but we’re not going to get
into that now.)
Here’s a little program to open a file using this system call:
142
Disk System Calls
Type in this program using DDT, save it as “test 100.ddt”, and then exe
cute it by typing -glOO. You should hear your disk drive click and D D T will
print out the usual star and ending address:
-g100
*0108 (Click!!)
W ow—what a powerful program —it made the disk drive click! W hat’s the
click mean? You’ve told BDOS to open the file whose name is in the FCB.
W hat name is in there? We don’t really know, since we haven’t put anything
in the FCB ourselves. Maybe there’s an old file name left over from a previ
ous operation, maybe not. W hatever is in there, even if it’s a string of
blanks or zeros, BDOS will try to find a file with that name. If it finds it,
BDOS will “ open” the file by recording the numbers of its allocation units
in the FCB. If it doesn’t find the file, it will still make the disk drive click by
looking for the file in the disk directory. (We’ll talk more about the direc
tory in Chapter 7.)
It’s not hard to see what’s in the FCB. From DDT, simply type “d5c,7f” . If
there’s nothing in the FCB but zeros, you’ll get a printout like this:
- d 5 c ,7 f
005C 00 00 00 00 ___
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .............................
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .............................
The first line shows the memory locations from 5C to 5F, and the next
lines, the locations from 60 to 7F. A name would be in here somewhere if
there was one, but there isn’t.
You can make sure a file name is placed in the FCB by simply calling up
D D T with the name of another program. For instance, assuming you have
saved the “ testlOO.ddt” program described above, you can put its name in
the FCB in the following way. First, exit from D D T with a -gO. Now, to get
back into DDT, and at the same time, load the new program into the TPA at
100 hex and leave the name o f the program in the FCB, type:
A > d d t testlOO.ddt
-d5c,7f
005C 00 54 45 53 .TES
0060 54 31 30 30 20 44 44 54 00 00 00 00 00 00 00 00 T100 DDT.............
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............................
There’s the name of the program!The name itself starts at location 5D, is 8
bytes long, and ends at location 64. The numerical ASCII values of the char
acters are shown on the left, and the actual characters on the right. Since
“TEST 100” is only seven characters long, the last space of this 8-space field
is filled in with a blank (20 hex) at location 64. The file extension “ D D T ”
occupies locations 65 to 67 hex.
If we now run our little program with a -glOO, it will find an existing file,
namely itself, in the FCB and open it.
Can we tell whether the Open File system call has been successful in find
ing the file whose name is in the FCB? Yes, by making use of what is called
the “directory code,” which is returned in the A-register following the call.
To use this feature, we need to add a few lines to our program to print out the
number returned in the A-register.
Type this program in using the “a” option in DDT, save it with “A > save
testlO l.ddt”, call it back in with “A > d d t testlO l.ddt” and run it with a
“glOO”. You should get the following on the screen:
-g100
3*0110
The “3” preceding the usual asterisk and ending address is the number
printed out by our program, and is the number that was in the A-register on
completion of the Open File system call. It might also be a 0, or a 1, or a 2.
Any of these numbers mean that the file, whose name was in the FCB, was
found by the Open File system call. (Whether the number is a 0, 1, 2, or 3 is
determined by the place in the disk directory that is actually occupied by the
file nam e—a topic we’re going to avoid until the next chapter.)
144
Disk System Calls
It would be more convenient if there were a way to place other file names
into the FCB directly, without having to enter them when calling up DDT.
This is the purpose of the “i” (for “Input”) command.
Suppose you have a program on your disk called TESTPROG.TXT. If
you’re already in DDT, and you want to put this name into the FCB, all you
need to do is type:
-itestprog.txt
Doesn’t that seem easy? Let’s try it out to see if it really works. First, we’ll fill
the FCB with “F F ’s” to make sure there’s nothing in it, and then we’ll use “i”
to fill in a program name. Then, we’ll check to see if it’s there.
-f5 c ,7 f,ff
-d5c,7f
005C FF FF FF F F ___
0060 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F F ...................
0070 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F F ...................
- i testprog. tx t
-d5c,7f
005C 00 54 45 53 .TES
0060 54 50 52 4F 47 54 58 54 00 FF FF FF FF FF FF FFTPR0G..........
0070 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F F ...................
The name is there, just asit was when we called it in with DDT. Notic
also that byte 0 at location 5C and byte 12 at location 68, which are the drive
number and “current extent,” respectively, are also zeroed out by the “i”
function. A “0” drive number stands for the “default drive,” which is the one
we’re in unless otherwise specified, and the “current extent” is 0 unless we’re
reading from a file that is more than 16,384 bytes long. (More on that later.)
It is possible to do “by hand” what the “i” command does, if we translate
the letters of the file name into ASCII values and put them into the correct
locations using the “s” command in DDT; but, of course, the “i” command is
far more convenient.
You can now check to see if your Open File program can find all sorts of
different programs. Use “i” to insert the name of a program that is on the
disk, and then run your program.
145
Soul of CP/M®
-i n o t a p r o g . c o m Nonexistent program.
- g 1 00
/ * 0 1 10 “/ ” means it wasn’t found.
If the file isn’t found, the A-register will contain F F when the program
returns from the Open File system call. The program should then print out a
slash (/), since this is the character with an ASCII value of 2F (30 plus FF),
which we calcuated in line 010A of the program. (FF has the value —1 in 8-
bit arithmetic, and 30 minus 1 is 2F.)
The easy solution is to read only very short records into the DMA. Since
D D T ’s stack grows down from the address FF, and records are read into the
DMA buffer at 80 and moving upward toward FF, if the records are short,
we can avoid conflict. We’ll try this just because it makes for a nice easy
146
Disk System Calls
iiiiiiiiiiiiiiiiiiiiiiin ii
///z///;////;;;/////;/;/////
TPA
0100
DDT Stack
lllllllllllllllllllllll
DDT's stack and the
- DMA buffer both try
to use this space.
///////////////////////
Zero DMA Buffer
Page 0080
FCB
005C
program, even though it has a built-in potential for disaster if we try to read
too long a record.
The first thing to do is to create a very short record. Call up your word-
processing program and type in some characters, but make sure you don’t
type more than a line (80 decimal characters). End your line with a “return.”
Save this as a file called “short.txt” . Since this file is so short, it is a 1-record
file. (It will show up as a 2K file in the directory, but only one 128-byte
record will be used.)
Now the only thing that stands between us and writing a program to read
this record into the DMA is learning the Read Sequential system call.
147
Soul of CP/M®
This system call looks a good bit like the Open File call, but note that it
reads a record, where the Open File call opened a whole file. W hat does
“sequential” mean? It means that the call will start off reading the first 128-
byte record in a file, will read the second one the next time it is called, the
third one the next time, and so on, until it comes to the end of the file.
How does it know when it’s at the end of a file? In either of two possible
ways. First, it can find an “end-of-file” mark in the file. This is only possible
if the file is a text file, so that one of the characters can be set to the ASCII
character for “end-of-file,” which is a 1A hex.
Secondly (and more important), since C P /M keeps track of how many
records are in a file and stores this information in the FCB when the file is
opened, all the Read Sequential system call has to do is keep track of how
many records it has already read to know if it has read the last record. The
total number of records in a file is stored in byte 15 (decimal) of the FCB,
and the number of the next record to be read is stored in byte 32. By com par
ing these two numbers, the Read Sequential call knows when it’s finished an
entire file.
If the Read Sequential system call does find an end-of-file marker, it sets
the A-register to a nonzero value on its return. Otherwise, the A-register is set
to 0, which indicates that the read was successful.
Reading a Record
Here’s the routine to read into the DMA the record that we created earlier:
“ short.txt” . (D on’t try to read anything longer, or bad trouble and error
messages will result.) Call this program “test 102.ddt” .
-a100
0100 mvi c,f Open file.
0102 Lxi d,5c Set FCB address.
0105 c a ll 5 Call BDOS.
0108 mvi c,14 Read record.
010A lxi d , 5c Set FCB address.
010D c a ll 5 Call BDOS.
0110 rst 7 Back to DDT.
Before running this program, you need to be sure that (1) your short file
“ short.txt” is on your disk, and (2) that you have put the name of this file
into the FCB by typing “ishort.txt” from DDT. Now, run it with a “-glOO” .
W hat happens? The disk drive should click. Big deal—no better than the last
program? No. This time there’s a tangible result.
148
Disk System Calls
Use “d” to look at the DMA buffer. Assuming that your “short.txt” file
consisted of the line “Now is the time for all good men to come to the aid of
their country,” you should see the following when you dump the buffer:
-d80,ff
0080 4E 6F 77 20 69 73 20 74 68 65 20 74 69 6D 65 20 Now i s t he t i me
0090 66 6F 72 20 61 6C 6C 20 67 6F 6F 64 20 6D 65 6E f o r a l l good men
00A0 20 74 6F 20 63 6F 6D 65 20 74 6F 20 74 68 65 20 t o come t o t he
00B0 61 69 64 20 6F 66 20 74 68 65 69 72 20 63 6F 75 a i d o f t h e i r cou
00C0 6E 74 72 79 2E 0D 0A 00 00 00 00 00 00 00 00 00
00D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 F0 00 00 00 00 00 00 00 00 C1 D6 9B D4 42 d4 44 00
j
The record ends with the codes for carriage return and linefeed: OD, OA.
The zeros after that could be any junk that happened to be in the buffer
before the record was read in, although you could guarantee they were zeros
by using “f” to fill the buffer before executing the program.
So, there’s the record, sitting safe and sound in the DMA buffer, just where
it should be. And the program to read it in is only 7 lines long! That’s even
easier than reading a record in BASIC! However, as you’re aware, we’re
restricted to very short records with this program. W hat we need now is a
way to avoid the conflict that results when the DMA buffer and the D DT
stack try to share the same space.
149
Soul of CP/M®
This system call looks easy enough to use, and it is. All we need to say is:
We chose 400 (hex) as the new DMA address simply because it’s high
enough in memory so that we know it isn’t going to interfere with our pro-
gram at 100 hex. Actually, you can put the DM A anywhere you want, as long
as it doesn’t interfere with your program or the C P /M operating system (or
DDT, if you’re using that).
Let’s incorporate these program lines into our read record program:
Save this program as “testl03.ddt” . Now, use it to read in the first record
of any file you want, no matter how long it is. Here’s how to do it:
If you want to read the second record of a file that is longer than one
record, simply repeat Steps 3 and 4. Each time you execute the program a
new record will be read into the DM A buffer, where you can look at it with
the “-d” command. You can examine any sort of record this way, whether it
contains text, hex values, or whatever. This can be useful in investigating
what’s really going on, on the bit level, in a record on your disk.
150
Disk System Calls
For instance, some word-processing programs use the high-order bit (bit 7)
of each character to indicate that there is something special about the charac
ter. A normal space is 20 hex, but the “soft” space used in some processors to
justify text lines might be represented by AO hex, since 20 hex is 0010,0000 in
binary, and A0 hex is 1010,0000. They’re the same, except that the high bit is
turned on in A0.
Dumping a record using D D T this way immediately reveals the difference
between hard and soft spaces, and a variety of other things as well. Read
different kinds of files and look them over. You may be surprised at what you
discover.
We’ve left two things out of our program: (1) the check to see if the file
we’re trying to open really exists, and (2) the check to see if the record we’ve
read is a valid record or an end-of-file. Let’s modify the program so that it
prints out the directory codes returned in the A-register twice; first when it
returns from opening the file, and second, when it returns from reading the
record. This way, we can tell if the file we are trying to read really exists, and
if we have read a valid record in the file or an end-of-file. We’ll call this
program “ testl04.ddt”.
- a 1 00
0100 mvi c,f open file
0102 Lxi d,5c
0105 ca L L 5
0108 mvi c,2 print resulting directory code
010A adi 30
010C mov e , a
010D ca L L 5
0110 mvi c,1 A set DM A to 400
0112 Lxi d,400
0115 ca L L 5
0118 mvi c,14 read record
011A Lxi d,5c
011 D ca L L 5
0120 mvi c , 2 print resulting directory code
0122 adi 30
0124 mov e , a
0125 ca L L 5
0128 rst 7 back to DDT
151
Soul of CP/M®
Let’s try out this program on the “short.txt” file that we created before.
After saving the program, reload it with DDT. Then, set up the FCB with the
“i” instruction and run the program.
- i short. tx t
- g 1 00
30*0128
Now we can dump the contents of the DMA to see that the record is really
there:
-d400,47f
0400 4E 6F 77 20 69 73 20 74 68 65 20 74 69 6D 65 20 Now i s t he t i me
0410 66 6F 72 20 61 6C 6C 20 67 6F 6F 64 20 6D 65 6E f o r a l l good men
0420 20 74 6F 20 63 6F 6D 65 20 74 6F 20 74 68 65 20 t o come t o t he
0430 61 69 64 20 6F 66 20 74 68 65 69 72 20 63 6F 75 a i d o f t h e i r cou
0450 6E 74 72 79 2E OD0A 00 00 00 00 00 00 00 00 00 n t r y ..........................
0460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...................................
0470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...................................
But, now, if we try to read the next record of this file, we find that a direc
tory code of 1 is returned, indicating an end-of-file:
- g 1 00
31*0128
^------------------------The “ 1” indicates that the record was not sucessfully read; it was an end-of-file.
Continuing to attempt to read records from the file will yield the same result:
a directory code of nonzero indicating end-of-file.
Try out this program on some more existent and some nonexistent files.
Check the contents of the DMA with the “d” command both before and after
reading a nonexistent file. They don’t change since there’s nothing in the file.
Read a file with a fairly small number of records and see how the contents of
the DMA change for each succeeding record. Watch the directory code
printout change from 0 to a nonzero value when the last record is reached.
152
Disk System Calls
Using this program with the D D T dump function, you can explore how a
variety of files are stored on the disk. You can find out what the contents of
your files really are, rather than just what your applications programs tell you
they are.
A > ty p e2 progname.ext
where “progname” is the name of the file you want to look at and “ext” is its
file type. Executing the program in this way has the effect of putting the
program name which follows “ type2” into the FCB, just as it did when we
called D D T and followed it with a program name. In fact, this is always true;
when you type two program names in a row following the A > prompt,
C P /M will load the first program into the TPA, and put the filename of the
second into the FCB.
Look at the listing of TYPE2 in the following pages. The program starts by
opening the file whose name is in the FCB and, then, checking to see if the
file was found. If not, it prints the message “N o such filename” and exits
back to C P/M . Assuming that the file exists, it reads the first record from the
file and checks to see if it is a valid record or an end-of-file (EOF). If it is the
EOF, the program figures its job is done and exits to C P/M . Otherwise, it
knows it has a valid record, which it prints out using the Console Out system
call in the subroutine “pchar” (for “print character”). We could have used
the Print String system call, but then we wouldn’t have been able to print
strings containing dollar signs, since these act as terminators to Print String.
Since this is a fairly long program which we don’t want to run from D D T
anyway, we’ll assemble it using ASM. The program is given in Listing 5-1.
153
Soul of CP/M®
; r e a d r e c o r d f rom f i l e
/
010C 0E14 nextr mvi c,readr
010E 115C00 lxi d , f cb
0111 CD0500 ca 11 bdos
f
; i f e n d - o f - f i l e , t hen e x i t t o CP/M
0114 B7 ora a ; i s a=0?
0115 CO rnz ; n o, so e o f , back t o CP/M
r
; d i s p l a y c o n t e n t s o f dma buffer
011 ó 218000 lxi h,dma ; s e t p o i n t e r i n hL
0119 0680 mvi b ,1 28d ; s e t c o un t i n b
011B 5E l o op mov e,m ; g e t c h ar f rom b u f f e r
011C CD3001 ca 11 p c ha r ;display i t
0 11 F 23 i nx h ; i n c r e m e n t hL
0120 05 dcr b ; d e c r e m e n t b-done?
0121 C21B01 jnz lo op ; not yet
0124 C30C01 jmp nextr ; y e s , go g e t n e x t r e c o r d
/
; n o such f i Le name: p r i n t message and e x i t
0127 0E09 nofi mvi c , p r i nts
0129 113A01 Lxi d , n f me s s
012C CD0500 ca 11 bdos
012 F C9 ret ; b a c k t o CP/M
154
Disk System Calls
;s u b ro u tin e to d i s p l a y one c h a r a c t e r on s cr ee n
0130 E5C5 p cha r push h ! push b! ;save r e g i s t e r s
0132 0E02 mvi c,conout / p r i n t character in
0134 CD0500 ca 11 bdos
0137 C1E1 pop b! pop h ! ; restore re g is te rs
0139 C9 ret
Most of the programming tricks used in this program you’ve seen before.
We’ve used a lot of EQU statements at the beginning of the program in an
attem pt to make the rest of the listing easier to read. A flowchart for the
TYPE2 program is given in Fig. 5-3.
In lines 108 and 109, we check for EOF by incrementing the directory code
in the A-register. If it was F F hex, indicating an EOF, incrementing it will
cause it to become zero, and our “jz ” (jump on zero) instruction will then be
activated and will cause control to go to “nofi” (for “no file”), where the “N o
such filename” message is printed out using the Print String system call.
You’ve used this system call before, but the way we type the message into the
listing, using the “db” directive, may be new to you.
The “D B ” Directive
The hex values for carriage return and linefeed are OD and OA, respec
tively. Thus, mess2 and mess3 are equivalent, provided the labels “cr” and
“If” have been set to these values with EQU statements.
156
Disk System Calls
After reading a record, TYPE2 checks for end-of-file by ORing the A-regis
ter with itself; if it’s not zero, the program returns to C P/M . To display the
contents of the DM A buffer on the screen, the program sets up a loop. The
B-register holds the count of characters in the buffer (which is 128 decimal)
and the HL-register holds the address of the particular characters in the
buffer. We can then ju st put a character in the E-register with a “mov e,m”
instruction and print it with a call to pchar. Next, we do a loop, incrementing
the HL pointer, decrementing the B-register counter, and printing the charac
ters, until the count is zero, at which time we go off to read another record.
Type the program in using your word processor, giving it a filename of
“ type2.asm” . (D on’t type the hex values on the left-hand side, of course.)
Assemble it with ASM, load it with LOAD, and you’re ready to execute it
directly from C P/M . Type “type2” followed by the name of the program that
you want printed out.
Terrific! It’s ju st like C P /M ’s “ type” command. You can even start and
stop the scrolling with control-s, just as you can with “type.” So look at you!
Now you’re writing C P /M system programs. In a few more pages, you’ll be
ready to apply for a job at Digital Research!
; p r i n t c h a r a c t e r i n a - r e g i s t e r on s cr ee n
01A6 D5C5E5 pchar push d ! push b ! push h ;save r e g i s t e r s
01A9 5F mov e ,a ;cha ra cte r in e
01 AA 0E02 mvi c,conout
01 AC CD0500 ca 11 bdos ; c a l l c ono ut r o u t i n e
01AF E1C1D1 pop h ! pop b ! pop d ; g e t r e g i s t e r s back
01B2 C9 ret
A
can it find this out, working only in binary? N ot so hard. It merely subtracts
10,000 decimal (2710 hex) from the number and checks to see if the result is
negative. If not, it knows that the number is larger than 10,000, so it subtracts
it again. If the result is still not negative, it knows that the number is larger
than 20,000. It keeps subtracting until the result is negative, at which point it
knows (since it’s been counting) just how many 10,000’s there are in the
number. It prints this out, since it’s the first digit of the decimal translation of
the number. Then, it restores the original number to the value it had just
before the subtraction that made it go negative; this is the number with the
10000’s digit removed.
Let’s say the number is 8000 hex, which we happen to know is 32768 deci
mal. When we subtract 2710 (hex) from B000, the result is not negative, as we
can easily see by performing the arithmetic in decimal: 32768 minus 10000.
So we do it again. We have to do it three times, so we print out 3 (which is
right) and restore the number to 0AD0 hex, which is 2768 decimal: the
number with the 10000’s digit removed.
Now we have a new num ber that we know m ust be less than 10,000, so
we can start subtracting 1,000 (one thousand) from it to see how many of
them there are. And, so on, until we’ve printed out the hundreds, tens, and
ones places. In 8080 assembly language, there isn’t a subtraction instruction
for 16-bit numbers, so we put negative numbers into the program when we
assemble it and add them with a “dad” instruction. This subtraction pro
cess is similar for each of the five values that we need to subtract, so we put
it in a subroutine that we will call each time with the value in the DE-
register.
Another part of this routine is tricky. Look at line 194. W hat we want to
do here is put the count of how many times we have had to subtract 10000 (or
1000, or 100, etc.). But, we want to be able to print this count out, so we want
it in ASCII and we want to start at —1 instead of 0, because we always
subtract the number the first tim e -th a t doesn’t count. So the clever way of
generating a —1 in ASCII is to specify ’0’ (that’s zero, not oh) in single
quotes, which gives us the ASCII code for zero, and then we subtract 1 from
it: ’0’- l .
Yes, you can write this kind of arithmetic statement in the operand field of
an instruction. ASM will carry out the arithmetic operations for you, unless
they get too complicated.
You may want to use the BIN IDEC subroutine in other programs that you
write. If so, just plug it in where it’s appropriate. All you need to remember is
to define “conout” and “bdos” at the start of your program, so it will know
where they are.
159
Soul of CP/M®
This instruction is somewhat like the STA instruction, in that it stores the
contents of a register directly into memory. However, STA stored the 8-bit
contents of the A-register, while SHLD stores the 16-bit contents of the HL-
register. Note that this means that two bytes of memory have to be set aside,
whereas only one was needed for STA. To do this, see the DW directive
described next. The place where the two bytes are to go can either be named
with a hex address, or with a symbolic name, either of which are pointers to
the first byte of the two-byte “word.”
Examples:
shld 2011
shld penny
“LH LD ” is the opposite of SHLD, as you may have guessed. It takes the
16-bit value from the referenced memory location and loads it back into the
HL-register.
Examples:
lhld 2011
lhld whistle
A / 0102
63 F9 22
/
0103 SHLD 2011
11
/ 0104
20
/
/
///////////////////
lllllllllllllllllll
/ 2010
Section of memory
where constant will
/ 2011
be stored.
/ 2012
/
After SHLD 2011 is executed:
Section of memory
containing program.
SHLD 2011
Section of memory
where constant is
stored.
161
Soul of CP/M®
0101
HL-register
z: 0102
/ 0104
20
///////////////////
lllllllllllllllllll
FF
2012
/ 0102
22
/
0103 LHLD 2011
/
0104
20
/
/
llllllllllllllllll
llllllllllllllllll
/ / 2010 Section of memory
where constant is
stored.
FF
/
2012
01
/
162
Disk System Calls
defines a word (which is two bytes) to be a specific 16-bit value. In our pro
gram, we need two locations to store 16-bit values because all the registers
are already used: “ctr” to store the number of linefeeds counted so far, and
“ temp,” in BINIDEC, to store the original number in HL before we subtract
from it each time. Both of these storage areas are reserved by using DW
directives.
Like DB, DW can define either numerical or character constants. Several
constants can be used together if they’re separated by commas.
addrs dw OffOOh
crlf dw ODOAh
abchar dw 4142h
busy dw 4142h,4344h
series dw 1000,1001,1002,1003
Fig. 5-6 gives a flowchart showing the operation of LINES. Using this
chart and the descriptions of the various sections of the program given ear
lier, you should be able to to figure out what the program is up to. The
LINES program is given in Listing 5-3.
; L I N ES - pr o gr a m t o p r i n t o u t number o f Li nes i n f i Le
163
Soul of CP/M®
164
Disk System Calls
; c o u n t number o f L i n e f e e d s i n r e c o r d s t o r e d i n dma
¥
0121 218000 Lxi h,dma ; p u t dma add r i n hL as p o i n t e r
0124 0680 mvi b , r e c s i z ; p u t r e c o r d s i z e i n b as c o u n t e r
r
0126 7E newch mov a,m ; g e t c h a r a c t e r f rom dma b u f f e r
0127 E67F ani mask ;mask o f f h i g h b i t
0129 23 i nx h ;increm ent p o in te r
012A 05 dc r b ;decrement c h a r a c t e r counter
012B CA1501 jz newrec ;when c o un t i s 0 , g e t new r e c o r d
012E FE1A cpi e of ; i s t h e c h a r a c t e r an e of ?
0130 CA4401 jz done ; yes
0133 FEOA c pi Lf ; i s the c h a r a c te r a Linefeed?
0135 C22601 jnz newch ; n o, g e t n e x t c h a r a c t e r
0138 E5 push h ; s a v e a d d re s s which i s i n hL
0139 2A5301 LhLd ctr ; i n c r e m e n t Line c ount
013C 23 i nx h ; ( u s i ng h L)
013D 225301 sh Ld ctr
0140 E1 pop h ; g e t a dd re ss back i n h i
0141 C32601 jmp newch ;go get next c h a r a c te r
r
; e n d - o f - f i L e , o r no f i Le , so p r i n t r e s u L t and e x i t
/■
0144 2A5301 done Lh Ld ctr ; g e t c o u n t i n hL f o r b i n i d e c
0147 CD7501 ca L L b i n i d e c ; p r i n t number o f L i nes i n dec
014A 115501 Lxi d,Lmess ; s e t up " L i n e s " message
014D 0E09 n o f i Le mvi c , p r i n t s ; p r i n t message
165
Soul of CP/M®
f
; b i n i d e c - c o n v e r t s b i n a r y number i n h i t o
; d e c i m a l , p r i n t s r e s u l t on s c r e e n .
; p r i n t c h a r a c t e r i n a - r e g i s t e r on s cr ee n
01A6 D5C5E5 p c ha r push d ! push b ! push h ;save r e g i s t e r s
01A9 5F mov e,a ;ch a ra cte r in e
01 AA 0E02 mvi c,conout
01 AC CD0500 ca 11 bdos ; c a l l c o no ut r o u t i n e
01AF E1C1D1 pop h ! pop b ! pop d ; g e t r e g i s t e r s back
01B2 C9 ret
f
166
Disk System Calls
01B5 end
Try typing in LINES, assemblying it, and running it on some of your test
files. N ot bad, eh? Now when someone asks you how many lines are in some
program that you’ve written, you can tell them exactly, in about three
seconds.
167
CHAPTER 6
In the last chapter, you learned how to read records from a disk file. In this
chapter, we’re going to move on to writing records and files to the disk. To
write to the disk, you’ll need to know the “Make File,” “Write Sequential
File,” and “Close File” system calls. After explaining these calls, we’ll illus
trate their uses with an example program, STORE, which is a very simple
word-processing program. We’ll show you two versions of the program, one
of which uses the “Delete File” system call.
169
Soul of CP/M®
The next area that we’ll cover is that of “random ” records. Random, in this
case, doesn’t mean that the records contain just any old random words and
numbers, it means that it’s possible to read or write to any record in a file,
without starting at the beginning of the file and without reading all the
records until you get to the one you want. Thus, “random ”is the opposite of
“sequential.” We’ll describe the “ Random Read” and “Random Write” sys
tem calls, and then use them in a program (RANDYM OD) which will permit
the modification of any record in a file.
In this section, we’re going to describe the three new system calls that are
necessary for writing a sequential record: Make File, Write Sequential, and
Close File. Since these calls are all necessary to do the writing operation,
we’re going to describe each of them before we go on to give an example
program.
MAKE FILE
In the last chapter, you learned that to read a record from a file, you had to
first “open” the file using the “Open File” system call. This was necessary,
first to tell the BDOS routines of C P /M what file you wanted to read the
record from (the file name passed in the FCB), and secondly, to tell BDOS to
determine where the various records of the file were so that they could be
easily accessed by subsequent Read system calls. BDOS then wrote the loca
tions of the records in the FCB in the area called “allocation units.”
170
Writing to the Disk
We’ll show how this code fragment is used when we get to the STORE pro
gram.
Before the “Write Sequential Record” call can be used, several conditions
must be met:
171
Soul of CP/M®
2. The file must have been initialized with a Make File system call. (It is
also possible to use the Write Sequential callwith an existing file that
has been initialized with an “Open File” system call.This isdescribed
later in this chapter.)
3. The record to be written must be in the 128-byte (128 hex) DMA
buffer. If the DM A buffer is not in the usual (or “default”) position at
80 hex, a Set DMA system call must have been issued to put it in the
appropriate place.
The format of the Write Sequential system call is similar to the Read
Sequential Record call. The directory code returned in the A-register is dif
ferent, in that a nonzero value indicates that the disk is full, a fairly rare
occurrence and one we won’t worry about with our small sample programs.
A typical section of code used to write a sequential record might be:
Like the Read Sequential system call. Write Sequential writes to the first
record of the file the first time that it is called, writes to the second record the
second time it is called, and so on. (That’s why it’s called “ sequential.”) How
does BDOS “remember” what record it’s supposed to write to next when it
executes this call? To understand this, we’ll need to look somewhat more
deeply into the workings of the FCB.
Look back at the diagram of the File Control Block in the last chapter
(Fig. 5-1). The “cr” byte is number 32 in the FCB, and is located at memory
location 7C (if the FCB is in its usual place). The purpose of this byte is to
keep track of what record is currently being written to (or read) by a sequen
tial write (or read) operation. Thus, when you execute a call to the Sequential
Write system call, the record in the DMA buffer will be written to the record
number that is in this cr byte. After either a write or a read operation, the
number in cr is incremented so that the next record will be written to the next
record in order.
This byte is typically set to zero by the user at the same time that the file is
opened (or “made”), so that the first record will be written into record 0, the
second into record 1, and so on.
172
Writing to the Disk
Extents
The next question is, “W hat’s an extentl” An extent is 128 records. Extents
are necessary because of the way C P /M keeps track of where the various
records of a file are stored on the disk. In general, a programmer doesn’t need
to know too much about this process, since C P /M takes care of it more or
less automatically. Again, we’ll postpone further discussion of these topics
until the next chapter, where we’ll talk about the disk directory.
For the time being, just keep in mind that the cr byte in the FCB is incre
mented each time a record is read or written sequentially, until the value in cr
reaches 127 decimal. At this point, a new extent is opened. This means that
the value in cr is reset to 0 and another byte in the FCB is incremented. This
is the “ex” or “current extent” byte, which is number 12 and is located at
address 68 hex. This byte, too, is normally set to zero when a file is first made
or opened for sequential operations.
173
Soul of CP/M®
After you have opened or “made” a file and started writing to it with Write
Sequential system calls, C P /M ’s BDOS takes care of deciding where on the
disk each record is going to be written. This information, which constitutes a
map of the disk showing where the various records of a file are stored, is kept
in memory as long as the file is “open” ; that is, currently being written to. If,
after writing a bunch of records, you ju st turned off your computer and
walked away, this “mapping” information would be lost forever and, then,
when you powered up again and tried to read the file from the disk, C P/M
wouldn’t know where to find it. It’s the purpose of the Close File call to make
sure this doesn’t happen.
When the Close File call is executed, C P /M takes the mapping informa
tion, which is stored temporarily in memory, and writes it onto the disk in a
region called the “disk directory.” When you next try to read this file, BDOS
will look for the file name in the directory on the disk and will write the
information it finds there back into memory. This is done by the Open File
system call.
The moral of all this is that after you have finished writing records to a file,
you had better close it, or you will never be able to read it back in. You don’t
have to close a file after reading it, however, because the directory has not
been changed. Thus, the directory information on the disk is still valid, even
though the directory in memory may be destroyed.
The format of the Close File call is similar to that of an Open File. Here’s a
section of code used to close a file:
Now we’re ready to put together the three system calls that we’ve just
learned into a real program to write something to the disk. Here’s the pro
gram:
-a100
0100 mvi c, 1a Set DMA address to 400.
0102 Lxi d ,4 0 0
0105 ca L L 5
0108 mvi c,16 Make file.
174
Writing to the Disk
Before we can execute this program from DDT, however, there are a number
of things that we need to do.
First, make sure that there is no file on your disk with the name
“newfile.txt” . Next, we need to insert the name of the file that we’re going to
be creating into the FCB. We’ll do this using the “i” operation. Thirdly, we
need to put something into the DM A buffer so that we can write it onto the
disk. This is a little cumbersome to do from DDT, but we’ll try it just to get
the feeling of what the routine is doing. For this, we’ll use the “s” operation.
Type in the program in DDT, save it as “ testl05.ddt” , and return to D D T
with the program (“A > d d t testl05.ddt”). Then, type in the following:
We won’t fill in all 128 decimal bytes of the DMA, since that would take too
long using the “s” operation. (Although you could fill the whole DMA buffer
with the same character using the “f” option.)
175
Soul of CP/M®
We end our message with a carriage return and a linefeed so that when we
print out the record it won’t be overprinted by the next line. And, most
importantly, the last character of our record is an end-of-file character: 1A
hex. This is necessary to tell BDOS that the record is finished, when we go to
read it back.
Now run the program and then exit to C P /M and see if the file is in the
directory:
-g100
*0120
-gO
A>dir n e w f i l e . t x t
A: NEWFILE.TXT
So far, so good. Now, let’s see what’s really in the record, using the C P /M
built-in TYPE function. (We can’t use TYPE2 here because it isn’t sophisti
cated enough to see the end-of-file mark in the middle of the record and it
will print out all the junk in the DMA buffer following the “ABCDE”
message.)
A>type n e w f i L e . t x t
ABCDE
A>
There it is! It may not be War and Peace, but it is the very first file that
you’ve written to the disk with your own program.
There’s another way to examine the contents of newfile.txt to see if it’s
really there. Call up D D T along with newfile.txt:
A>ddt n e w f i l e . t x t
When D D T is loaded, dump the DMA buffer, which is where the first
record of a file is loaded when the file is called in with DDT:
-d 8 0,ff
0080 41 42 43 44 45 0A 0D 1A 51 01 CD 8C 00 C3 51 01 ABCDE.. . Q............Q.
^ ..........■! || | ,T —...... J I w >
First record of newfile.txt ASCII version
176
Writing to the Disk
There it is! This is a good way to examine files that have only one short
record.
Now we’re going to expand this little program into one that you can actu
ally use as a mini word processor.
A>store n e w f i l e . t x t
T h i s i s t he Li ne t h a t you t y p e i n , t o be s t o r e d on d i s k
Type a n o t h e r Line i n t o n e x t r e c o r d .
When the program is executed, it first finds out if the file name you typed
in following “store” exists or not. If it does, the program will read (without
printing) to the end of the file, wait for you to type something in, and then
write it at the end of the file. If the file doesn’t exist, the program opens a new
file with that name (using the Make File system call), waits for you to type
something in, and then writes it at the beginning of the file.
We have made the restriction that you can only type in one line—no more
than 80 characters—at a time. This avoids a lot of complexity about how to
handle “returns” embedded in the text. At the end of your line, you must
press “return” to tell the program that you’ve finished. It will then write the
line to the disk as a single record. If you press “return” without writing any
thing, the program will assume that you’ve finished with the entire file and
will close the file and exit to C P/M .
177
(
Soul of CP/M®
; f i L L t h e dma b u f f e r w i t h d e l e t e marks
0114 218000 newrc lx i h,dma ; p u t dma a dd r es s i n h I
0117 0680 mvi b ,1 28d ; p u t c o un t i n b
0119 36FF lo op mvi m, d e l ; s t o r e d e l e t e i n memory
178
Writing to the Disk
011B 23 i nx h ; i n c r e m e n t hL p o i n t e r
011C 05 dcr b ; d e c r e m e n t count
011 D C21901 jnz Loop ; n o t done y e t
r
; p r i n t a Linefeed
0120 0E02 mvi c,conout
0122 1E0A mvi e , Lf
0124 CD0500 ca L L bdos
ł
;read ch aracters in to b u f f e r f rom keyboard
0127 3E50 mvi a, 80 d ; s e t c o un t t o s cr ee n w i d t h
0129 327E00 sta dma-2 ; ( s trin g buffer s ta rts 2
012C 0E0A mvi c,reads ; b y t e s b e f o r e dma
012E 117E00 Lxi d,dma-2 ; b u f f e r , t o Leave room f o r
0131 CD0500 ca L L bdos ; ma x- co unt and c o u n t )
r
; f i nd o u t i f b u f f e r i s empty-i f so, exi t
0134 3A7F00 Lda dma-1 ; g e t number o f ch ar i n p u t
0137 B7 ora a ; i s i t 0?
0138 CA5201 jz f i n i to ; yes
; i n s e r t c r and I f i n b u f f e r f o l L o w i n g t e x t
013B 5F mov e , a ; c h a r a c t e r c o un t i n de
013C 1600 mvi d,0 r
013E 218000 Lxi h,dma ;dma a dd r es s i n h L
0141 19 dad d ; ad d c o un t t o a d d r , p u t i n hL
0142 360D mvi m,cr ;s to r e ca rriage re turn
0144 23 i nx h ;increm ent p o in te r
0145 360A mvi m, Lf ; s t o r e Linefeed
; w r i t e record to d is k
0147 0 E1 5 mvi c , w r i t e r ; w r i te i t
0149 115C00 Lxi d , f cb
014C CD0500 ca L L bdos
014F C31401 jmp newr c ; g o read n e x t r e c o r d
;c lo s e f i l e before e x it in g
0152 0E10 f i n i t o mvi c,cLosef
0154 115C00 Lxi d , f c b
0157 CD0500 c a l l bdos
015A C9 ret ; b a c k t o CP/M
: f i l e a l r e a d y e x i s t s , so read t i l l EOF
179
Soul of CP/M®
Type the program in with your word processor, assemble it, and try it out,
before you read the explanation of how it works. This will give you an idea
what the program is supposed to do, which will be helpful in understanding
the following comments. Fig. 6-1 is a flowchart of the program’s operation.
We will first try to open the file. If it already exists, we jum p down to the
“alex” label at line 15B and print the following message to the user: “Text
will be added to file.” This will alert her to the fact that she’s not writing into
a new file. Then, we read the first record of the file and check to see if there’s
an EOF. If not, we read the next record and check again. If there is an EOF,
we go back to line 114, where we would have started if the file had not existed
in the first place.
Since our input lines are 80 character or less, and the records we’re writing
into are 128 characters, we’re going to have left-over space in the record. We
don’t want these extra characters to appear on the screen, or cause the printer
to do anything weird if we want to print out the file, so we want to ensure
that the rest of the buffer after our typed-in line is filled with some harmless
character. A good harmless character, and one that we can send to the printer
without causing any action at all, is the “rubout” character, which is 7F hex
(FF hex if we include the high-order bit). The section of code from 114 to
11D fills the entire DM A buffer with rubouts before any characters are typed
in by the user, thus ensuring that there will be no printable garbage following
the text.
We do a linefeed and, then, at line 127, read the characters from the key
board into the DMA buffer. Since we’re not using DDT, we can put the
DMA buffer where it’s supposed to go at 80 hex (its default address), which
we tell the program about with an EQU directive. We use the Read String
180
Writing to the Disk
system call, which requires that the maximum count of words be set two
addresses before the first character, and which fills in the number of charac
ters that are actually read in from the keyboard at the address that is one
byte before the first character.
If the buffer is empty after this call, we know the user is done and we close
the file and exit to C P/M . Otherwise, we write a carriage return and linefeed
into the buffer following the text, so that each record typed in doesn’t over
print the one before it. Then, we write the record to the disk and go back up
to “newrc” to get another record from the keyboard.
181
Soul of CP/M®
Now you can type in files using STORE and can read them out again using
TYPE2. As text editors go, this may not be too fancy, but it does give you the
satisfaction of having written it all yourself and, we hope, of understanding
how the programs work.
Use STORE to create a file called “sample.txt”, which contains 10 or 12
records, by typing in 10 or 12 lines one after the other (separated only by
single carriage returns). Check that the whole record is there, using TYPE2.
We’ll use this file later to experiment with the Random Read and Random
Write system calls.
Here’s an example of how this call can be used. In the STORE program,
we assumed that, if a file already existed, the user would want to add the
records that he typed in to the file. We could have made another assumption:
that he wanted to erase whatever was in the file and start over with what he
was typing in.
Let’s use the Delete File system call to modify the STORE program to do
this. Do the following:
182
Writing to the Disk
A>pi p s t o r e 2 . a s m = s t o r e . a s m
3. Remove the program lines from 100 to 109 and substitute the following:
;d e le te t he f i Le
mvi c , d e f i Le
Lxi d,fcb
caLL bdos
RANDOM RECORDS
If you want to read or write a file, starting at the first record and going on
until you come to the end, then the “ Read Sequential” and “W rite Sequen
tial” system calls which we have already described are just what you need.
But suppose you want to read a record that is in the middle of a file? Or, you
want to modify a record in the middle of a file? You can do it using the
sequential read and write calls, but it’s a little difficult. W hat’s difficult about
it? Mostly the way that the record numbers are specified in the FCB.
As you recall from the last section, there is a special byte in the file control
block to indicate what record is currently being written. When 127 decimal
records have been written, this byte “overflows” and is reset to 0, and another
byte, the “current extent” byte, is automatically incremented. The use of two
separate bytes to specify what record we’re talking about makes disk access to a
183
Soul of CP/M®
particular record somewhat more awkward than it might be. For instance, if we
had a program that wanted to read the 300th record in a file, it would have to
first determine what “extent” this record was on, by dividing 300 decimal by 128
decimal. Since 300 divided by 128 is 2, with a remainder of 44, our program
would have to set the extent byte to 2 and the current record byte to 44 decimal
184
Writing to the Disk
(2C hex). This isn’t impossible, but it does complicate things, so Digital
Research (starting with release 2.0 of CP/M ) has provided a set of system calls
which use a single 16-bit value to specify the record number.
Look back at the map of the FCB in the last chapter. Bytes 33, 34, and 35
(locations 7D, 7E, 7F) constitute the “random record num ber” and are
called, respectively, rO, rl, and r2. This is shown in Fig. 6-3. The last byte, r2,
is not used in C P /M systems (although it is used by M P/M ). In C P /M sys
tems, it must always be set to zero, otherwise error messages will result. The
bytes rO and rl constitute the 16-bit record value, with the rO byte represent
ing the least-significant byte and rl the most-significant byte.
However, using random reads and writes requires one more step than sequen
tial reads and writes. The program must place the 16-bit number of the record
to be accessed into bytes rO and rl before the read or write takes place.
We’ll briefly describe the Read Random and Write Random system calls
and, then, go on to show how they’re used in the RANDYM OD program.
When BDOS executes this call, it first looks at the record number in bytes
rO and rl to see what record the program wants to read. It then figures out
what “extent” the record is in, and the record number in the extent, and sets
7D 7E 7F
rO r1 r2
least- most-
significant significant
byte byte
always 0
16-bit record number
Fig. 6-3. The “ random record number.'
185
Soul of CP/M®
these bytes in the FCB. Then, it reads the record just as the Sequential Read
system call did.
An im portant difference between random and sequential system calls is
that the random calls do not automatically advance the record number each
time they are called. So, if you do a random read and, then, do another ran
dom read, you’ll be reading the same record twice unless your program has
incremented the random record number in rO and rl before the second read.
Also note that, since Random Read has automatically set the current
extent and current record bytes in the FCB, you could then do a Sequential
read to read the same record again, followed by subsequent records, if you
wished. This gives you the capability to plunge into the middle of a record
with random access and, then, read a number of records, starting at that
point, with sequential access.
The “Return Code” returned in the A-register following this call can actu
ally have a number of nonzero values, depending on what type of error has
occurred:
-a100
0100 mvi c,1a Set DMA to 400.
0102 Lxi d ,4 0 0
0105 ca L L 5
0108 mvi c,f Open file.
010A Lxi d,5c
010D ca L L 5
0110 mvi c,21 Read random.
0112 Lxi d,5c
0115 ca L L 5
0118 mvi c,2 Print error code.
011A adi 30
011 c mov e,a
011 D ca L L 5
0120 rst 7 Return to DDT.
Save this program as “ testl06.ddt” and then bring it back into memory
with DDT. Use the “i” command to set the filename of a file that you know
186
Writing to the Disk
has a number of text records in it, such as the “sample.txt” file that we sug
gested you write in the section on the STORE program. Then, use the “s”
command to set a record number into rO rl that you know is in the file. In
other words, don’t make the record number so large that it doesn’t exist; if
“sample.txt” is 12 records long, put in a number less than that.
A>ddt t e s t 1 0 6 . d d t
Execute the program and then look at the DMA with a “d400” to see
what’s been read in. Now, change the rO byte in 7D to some other record
number, like a 2 or a 4. If you have file that you know is longer than 256
records, you can change both rl and rO. For record number 350, for example,
you’d convert to the hex number 015E and then put 5E into rO (location 7D)
and 01 into rl (location 7E).
Read a number of records by changing the record number and see what
happens. Ordinarily, the “return code” printed out by this program will be
zero, but if you try to read a nonexistent record, you’ll get one of the codes
referred to above.
187
Soul of CP/M®
This call is similar to the Read Random call except, of course, that the
record to be written must already be in the DM A buffer. The error codes are
the same except that there is a new error, 05, which indicates that a record
cannot be written due to directory overflow.
As with Random Read, the current record and current extent bytes are
changed to correspond to the random record number given, but none of these
numbers is incremented.
Here’s a short D D T program showing how this call can be used. This pro
gram assumes that we want to create a new record, so it uses the Make File
system call. N ote that it’s necessary to close the file after writing to it.
-a100
0100 mvi c,1 a Set DM A to 400.
0102 Lxi d,400
0105 ca L L 5
0108 mvi c , 16 Make file.
010A Lxi d,5c
010D ca L L 5
0110 mvi c,22 Write random.
0112 Lxi d , 5c
0115 ca L L 5
0118 mvi c,2 Print return code.
011A adi 30
011 c mov e,a
011 D ca L L 5
0120 mvi c,10 Close file.
0122 Lxi d , 5c
0125 ca L L 5
0128 rst 7 Return to DDT.
Save the program as “test 107.dd t” and then load it back in with DDT. Use
the “s” command to fill in the DM A buffer from 400 to 47F with whatever
ASCII characters you want. Since we’re starting a new file, we assume we’re
going to write to the first record, whose number is 0000, so use “ s” again to
fill in 0 values in rO, r l, and r2. Use the “i” command to set the name of the
new text file (you can call it newfile2.txt) into the FCB and, then, run the
program. Exit from D D T and check the dirctory to see if the new file is there.
188
Writing to the Disk
Print it out using “TYPE.” It should be the same as whatever you put in the
DMA buffer.
- a 1 00
0100 mvi c , 1a Set DMA to 400.
0102 Lxi d,400
0105 ca L L 5
0108 mvi c,0f Open file.
010A Lxi d,5c
010D ca L L 5
0110 mvi c,22 Write random.
0112 Lxi d,5c
0115 ca L L 5
0118 mvi c,2 Print return code.
011A adi 30
011C mov e,a
011D ca L L 5
0120 mvi c , 10 Close file.
0122 Lxi d,5c
0125 ca L L 5
0128 rst 7 Return to DDT.
Save this as “testl08.ddt” and try it out, using the same steps as previously.
As with the Read Random system call, you must be sure that the record
number you put in rO and rl actually exists.
A>randymod t e s t f i l e . t x t
189
Soul of CP/M®
The file name must be one which exists, or the program will print “N o such
filename,” and return to C P/M . The program now waits for you to type in
the record number, in decimal, that you wish to modify in the file. Make sure
that this record exists, because the program does no checking to make sure it
has been given a valid record.
You can use the “sample.txt” file that you created in the section on the
STORE program for this. If “sample.txt” is 12 records long, set the record
number you want to modify to a number less than that: say 3. Once the
program has read the record, it prints it out on the screen, and then asks “OK
to modify (y/n)?” If you answer anything other than “y”, the program
returns to C P/M . Otherwise, the program waits for you to type something in.
As with the STORE program, you must type in a line that is less than 80
decimal characters long and terminate the line with a “return.” Listing 6-2
shows the RANDYM OD program.
Next, in Fig. 6-4, we have the flowchart for the RANDYM OD program.
The listing for RANDYM OD should be fairly easy to follow since it con
sists mostly of routines and code fragments that have been covered before.
However, there are one or two unusual items.
We can’t put the DMA buffer at the usual place from 80 hex to FF hex
because we want to use the Read String system call to get the input from the
user. Why doesn’t this work? Because Read String uses two bytes immedi
ately preceding the actual buffer: one to store the maximum number of char
acters and the second to store the actual number of characters that are typed
in. Unfortunately, the two bytes immediately preceding 80 hex are 7E and
7F, which are the bytes used to store rl and r2 of the random record number.
We could move the FCB, or move the DMA, or use a different way to read
the characters from the keyboard, but moving the DMA seems easiest, so
that’s what we do in lines 100 to 105. We move it to a place that we label
“dm a” at the end of the program, which we specify as 128 decimal bytes
using the “ds” directive.
Notice how we do arithmetic with the symbolic labels in the address field
in lines 117 and 11C. We know that byte rO is byte number 33 decimal in the
FCB, so instead of figuring out what this address is and writing it in the
address field, we write “fcb + 33d” . The assembler takes care of determining
where “fcb” is, adding 33 to it, and translating the result into an address for
the “shld” instruction to store in rO and rl. A similar process takes place for
the “sta” instruction in line 11C.
As we explained in the section on the STORE program, it’s necessary to fill
the DMA buffer with delete marks before accepting input to it from the key
board, so that any junk remaining in the buffer between the end of the user’s
190
Writing to the Disk
; d i s p L a y c o n t e n t s o f dma b u f f e r
012A 21EB01 Lxi h,dma ; s e t p o i n t e r i n hL
01 2D 0680 mvi b ,128d ; s e t c o un t i n b
0 1 2 F 7E d Loop mov a,m ; g e t c har f rom b u f f e r
0130 CDB701 ca L L pchar ; d i spLay i t
0133 23 i nx h ; i ncrement hL
0134 05 dcr b ; d e c r e m e n t b - done?
0135 C22F01 j n z dLoop ; not yet
; f i LL t h e dma b u f f e r w i t h d e L et e marks
014B 21EB01 Lxi h,dma ; p u t dma b u f f e r a d d re s s i n h L
014E 0680 mvi b ,1 28d ; p u t c o un t i n b
0150 36FF f Loop mvi m,deL ; s t o r e d e Le te i n memory Loc i n h i
0152 23 inx h ; i n c r e m e n t hL p o i n t e r
0153 05 dcr b ; d e c r e m e n t c oun t
0154 C25001 jnz f Lo op ; n o t done y e t
/
;read characters in to b u f f e r f rom k eyboard
0157 CD9001 cal L perLf ; p r i n t Linefeed
015A 3E50 mvi a ,8 0d ; s e t max c o un t t o s c r e e n w i d t h
015C 32E901 s t a dma-2 ; ( r e a d s t r i n g b u f f e r s t a r t s two
0 1 5 F 0E0A mvi c , r e a d s ; b y t e s b e f o r e s t a r t o f dma
0161 11E901 Lxi d,dma-2 ; b u f f e r t o Leave room f o r
0164 CD0500 ca L L bdos ; ma x- co unt and c o u n t )
192
Writing to the Disk
; i n s e r t c r and Lf i n b u f f e r f oL L ow in g t e x t
0167 3AEA01 Lda dma-1 ; g e t number o f c h ar s t y p ed i n
016A 5F mov e , a ; p u t i t i n de
016B 1600 mvi d , 0 /
016D 21EB01 Lxi h,dma ;dma a d d re s s i n hL
0170 19 dad d ; ad d c o u n t t o a d d r , r e s u L t i n hL
0171 360D mvi m , cr ;s to re carriage return
0173 23 i nx h ;increment p o in te r
0174 360A mvi m, Lf ; s t o r e Linefeed
; w r i t e record to d is k
0176 0E22 mvi c , r a nd w
0178 115C00 Lxi d , f c b
017B CD0500 ca L L bdos
;cLose f i L e before e x it in g
017E0E10 mvi c,cLosef
0180 115C00 Lxi d,fcb
0183 CD0500 caLL bdos
0186 C9 ret ; b a c k t o CP/M
;subroutine t o p r i n t c a r r i a g e r e t u r n and L i n e f e e d
0190 3E0D pcrLf mvi a,cr ; p r i n t carriage return
0192 CDB701 caLL pchar
0195 3E0A mvi a,Lf ; p r i n t Linefeed
0197 CDB701 caLL p cha r
019A C9 ret
193
Soul of CP/M®
typed-in line and the end of the buffer will not cause odd effects when it is
printed out.
The DECIBIN routine at the end of the program is the same one that you
saw before in the chapter on console system calls.
Try out the program using the “ sample.txt” file described in the section on
the STORE program. Use TYPE2 to examine the file, and then use
RANDYM OD to modify one of the records in the file. It’s easy to figure out
194
Writing to the Disk
which record number you want to modify, since each line in the file corre
sponds to one record.
N ot bad, is it? Using STORE, TYPE2, and RANDYM OD you can create,
examine, and modify text records, just as if you were using a real word
processor. Well, not really quite as well, but by now you’re such a good 8080
programmer that you can probably figure out how to combine these three
195
Soul of CP/M®
programs and add the features necessary to come up with a competitor for
WordStar!
We’re going to cover two more system calls in this chapter, both of which
are concerned with what happens when we change in midstream from read
ing files sequentially to reading them randomly, and vice versa.
This is a useful little system call that makes it easy for you to get to the end
of a file. This is useful if you want to add something to the end of a file and
don’t want to waste the com puter’s time in reading all the records sequen
tially to find what the number of the last record is.
For instance, take a look back at the STORE program earlier in this chap
ter. Here, if we wanted to add a record to an existing file, we had to read (in
lines 163 to 16C) all of the records in the file until we came to the end. To see
how the Compute File Size system call works, try modifying the STORE pro
gram as follows:
A>pi p s t o r e 3 . a s m = s t o r e . asm
This system call is similar to the Compute File Size call, except that it finds
the random record number in the middle of a record rather than at the end.
This is useful if you have been reading through a file using the Read Sequen
tial system call and, suddenly, find a record whose number you want your
197
Soul of CP/M®
program to remember. Executing this call will put the random record number
into rO and r l, where it can be extracted and stored by your program. Or,
your program can make use of the information in rO and r l to perform a
random write operation at this point in the file.
198
Writing to the Disk
OUT OF INK
That finishes up our description of the various system calls used to write to
the disk. In the next chapter, we’re going to delve deeper into the murky
world of disk directories, allocation units, blocks, tracks, and sectors. We’ll
learn how wildcards work and how to rescue a file that has been mistakenly
erased. We’ll finish off the chapter with the W ORDS program, which counts
the words in a file, or in a whole group of files if wildcards are used in the file
name. So don’t go away—things are just getting interesting!
199
CHAPTER 7
Soul Searching
W ildcards and the Disk D irectory
Using these ideas, we’ll then cover the “ Search for First” and “Search for
Next” system calls, whose purpose is to provide your program with informa
tion about particular files. These calls give us the tool we need for our next
example: a procedure which permits you to restore a file that has been acci
dentally erased.
We’ll finish off this chapter with WORDS, a program that counts the
number of words in a text file. W ORDS permits the use of wildcards in its
input; that is, it will list the number of words in a whole group of files. In this
program, which is somewhat more ambitious than any we’ve looked at so far,
we’ll also introduce the idea of stack management.
In addition to teaching you about the disk system, this chapter will give
you the skills necessary to write a variety of such advanced utility programs
as a directory program that tells you how much disk space a program occu
pies, a master directory program that will read all your disks one after the
other and produce a file of all your programs, and a program that restores
erased files. You could even write a disk utility program that would enable
you to read and write to specific sectors of the disk, as is done in many “disk
debugger” programs.
Things would be easy if all the records in a file were just assigned to a
group of sectors in order, so that a file with 100 records would simply occupy
(say) sectors 450 to 550—as if all cars of the same color (representing a file)
occupied one area of the parking lot. Unfortunately, things aren’t that simple
because of the way files actually get created. Fig. 7-1 gives a picture of how
two files are probably not arranged.
/ / / = file 1
+ + + = file 2
In the real world, however, this is what might happen. Suppose that
you’re using your word-processing program to write both a personal letter
and an 8080 program on the same disk. You might work a little on the
program and, then, a little on the personal letter, and then some more on
the program. C P /M doesn’t know, in advance, how long either of these
documents is going to be (you probably don’t either), so it doesn’t try to set
aside a big group of sectors for each file. As you type in parts of the pro
gram, it assigns sectors to this file, and as you type in parts of the letter, it
assigns sectors to that file. As a consequence, the sectors for the two files
get mixed up, like red and green cars parked random ly all over the lot. The
problem then is, how is C P /M going to keep track of what sectors hold the
records for a particular file? It’s like asking the parking lot attendant to give
you a list of the locations of every green car in the lot. The diagram given in
Fig. 7-2 illustrates how the sectors of two files are random ly distributed on
a disk track.
/ / / = file 1
+ + + = file 2
Allocation Units
Here’s a copy of the diagram of the file control block that we discussed in
Chapter 5. (It is reproduced here for your convenience.)
203
Soul of CP/M®
204
Soul Searching
This 36-byte file control block (FCB) is very similar to the information
that C P /M keeps in a place on the disk called the “disk directory.” At least
one such FCB-like entry is recorded in the directory for each file. It contains
all the information that C P /M needs to know about the file and where it’s
located on the disk.
Notice that bytes 16 to 31 of the FCB are occupied by something called
“allocation units.” This rather ponderous name holds the key to the way that
C P /M keeps track of where the sectors are in a file.
An allocation unit is nothing more or less than a group of 8 sectors on the
disk. The designers of C P /M decided it would be too complicated for the
operating system to try to remember the location of every single individual
record, so they grouped each eight sectors together and gave them a number,
and then made up a rule: all eight sectors in a particular allocation unit must
be occupied by records in the same file. It’s as if each block of eight parking
spaces in the lot had to be occupied by cars of the same color. This cuts down
the information the operating system needs to remember about each file and
speeds up disk access to the file, but it also means that there will (usually) be
unused sectors at the end of each file. For example, a file 10 records long will
occupy all of one allocation unit, but only two sectors of the next one, leaving
6 unused sectors which can’t be used by any other file. However, this is a
small price to pay for simplifying the disk directory entries.
In the FCB, there are sixteen bytes set aside to hold the allocation units.
Each allocation unit is represented by a single byte. This works because there
are approximately 2002 sectors, and 2002 sectors divided by 8 sectors per
allocation unit gives 250 allocation units. Thus, one byte (which can have
values from 0 to 255) can be used to specify any one of the 250 allocation
units. Some of these sectors are used by the system, leaving 242 for the user’s
programs. So the sixteen bytes in the FCB (or the directory entry) can refer
to 16 allocation units; this is 16 times 8, or 128 sectors, which can hold 128
records. (There can be more than 128 records in a file if different “extents”
are used. We’ll look at that soon.)
As we noted, the preceding description only applies if the disk system
being discussed is single density. If it is a double-density system, then,
instead of 16 one-byte numbers to refer to the allocation units, there are 8
two-byte numbers, with each one able to refer to any one of 256 times 256, or
65536, possible allocation units.
When you “open” a file, the operating system gets the information about
which allocation units the file occupies from the disk directory and writes this
information into the FCB. Let’s see how this process looks in the real world.
205
Soul of CP/M®
-a100
0100 mvi C,f Open file.
0102 Lxi d,5c
0105 c a ll 5
0108 rst 7 Back to DDT.
Now, since we want to see what happens to the FCB when we do various
things to it, let’s fill it with all bits (FF bytes) so we can start with a clean slate.
Now dump the FCB area to see that it really is filled with FFs:
-d5c,7f
005C FF FF FF F F ___
0060 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F F ....................................
0070 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F F ....................................
Looks all right. Now put the name of a short program into the FCB with
the “i” command. We’ll use a program called “multi.txt” which is three
records long. (You can create records of any length using the STORE pro
gram, since each line typed into this program is a record).
-im u lti.tx t
Now, dump the contents of the FCB again to see what’s there. (We’ll also
provide some reference numbers so that you can see more easily what bytes
are where.)
c D E F
Drive code
Current extent
005C 00 4D 55 4C .MUL \
0060 54 49 20 20 20 54 58 5400FFFFFF FFFFFF FF T I TXT
0070 FF FF FF FF FF FF FF FFFFFFFFFF 00 FFFFF F ..................
Current record
0 2 3 4 5 6 7 8 9 A B C D E F
206
Soul Searching
The name and file extension have been filled in from bytes 5D to 67 and,
in addition, byte 5C (which is the drive code), byte 68 (which is the current
extent), and byte 7C (which is the current record) have all been set to 0. The
“i” command does this automatically, since this is what BDOS requires when
the file is opened.
Now we’ll open the file by executing our little program.
- g 1 00
*0109
And now we’ll dump the FCB again and see what’s happened:
C D E F
■Record count
■Allocation unit
d 5 c. 7f
005C 00 4D 55 4C .MUL
0060 54 49 20 20 20 54 00 T I TXT.
0070 00 00 00 00 00 00 F F .....................
0 l 2 3 4 5 7 8 9 A B C D E F
Lots of zeros, and a few other goodies. Byte 69 has become 0, and byte 6A
is 80. (BDOS uses these bytes internally: don’t worry about them.) Byte 6B
(which is the “rc” or record count byte) is 03, which is the total number of
records in the file. Interesting. Bytes 6C to 7B are the 16 bytes that hold the
allocation units and they’re all 0, except for the first one, which is 61.
Since the file is a mere 3 records long, it’s only going to use one allocation
unit, since each allocation unit can hold up to 8 records. And that’s exactly
what we see; all the allocation unit bytes are zero (meaning they’re unused)
except one. BDOS can look at this FCB (so can we) and know immediately
that the 3 records of the file “multi.txt” are located in allocation unit number
61 (hex), which is one of the 242 (decimal) or so allocation units on the disk.
So if we issued a “read record” system call, BDOS would know which sectors
to read to get this file.
Extents
We’ve found that our 16 allocation units can hold 8 times 16, or 128 records.
This is 128 times 128, or 16384 bytes, a rather large file. But what happens when
a file is longer than that? Then, instead of simply assigning more allocation units
207
Soul of CP/M®
to increase the size of the file, we have to do a more major operation called
“opening a new extent.” W hat’s this mean? Remember that the information that
we read into the FCB when we opened the file came from the disk directory. It
was, in fact, an FCB-like entry in the directory, and if you looked in the direc
tory (as we’re about to do), you would see it sitting there.
Now, when a file exceeds 128 records, what we have to do is make another
entry in the directory. We use the same filename and type, but we have the
extent number (byte 6A) set to 1 instead of 0 (or to 2, or 3, or more, if that
many extents are required). Thus, a file with 300 records would have 3
extents and, thus, 3 entries in the directory: one for the first 128 records, one
for the next 128 records, and one for the 44 remaining records.
Fig. 7-3 gives a picture of how records, allocation units, sectors, extents
and so on are related. N ote again, that if you are using a double-density disk,
these numbers will be somewhat different. In double density, a sector can
hold either 256, 512, or 1024 bytes, depending on the system. The other num
bers given in the diagram of Fig. 7-3 change accordingly. However, records
will always consist of 128 bytes regardless of the system. This means that
when you write a program, you don’t need to worry about single density,
double density, or whatever other peculiarities the system may have: you sim
ply deal with 128-byte records.
1 DISK = 77 tracks x 26 sectors/track = 2002 sectors
208
Soul Searching
The disk directory, itself, is stored on the disk and consists of a number of
records. How many? In single density, there are 16 records, each of which can
hold 4 directory entries, for a total of 64 directory slots. In double density,
there are 32 records, each of which can hold 4 directory entries of 32 bytes,
for a total of 128 directory slots. This means that it is impossible to put more
than 64 (or 128) files on a disk, even if they are all so short that they have no
trouble with disk space.
You may be wondering where the last 4 bits went, if the FCB is 36 bytes
long and the directory entry only holds 32. W hat happens is that when a file
is opened, only 32 bytes are read from the directory to the FCB. The “cr”
(current record) byte and the 3 bytes which make up the random record
number (rO, rl, and r2), are constructed by BDOS when they are needed, and
are not a part of the permanent record of the file.
Here’s what a directory entry looks like:
Byte Number
(dec) (hex) Contents
0 00 00 Set to 00 if file is valid; set to E5 if file is erased.
1 01 T
2 02 E
3 03 S
4 04 T Filename, 8 bytes.
5 05 F
6 06 I
7 07 L
8 08 E
9 09 C
10 0A O
11
12
0B
0C
M
00
} File extension, 3 bytes.
Extent.
13 0D 00 si
14 0E 00 s2
15 OF 12 Num ber of records in this extent.
16 10 3C
17 11 3D
Allocation units (unused spaces = 0).
18 12 00
19 13 00
209
Soul of CP/M®
20 14 00
21 15 00
22 16 00
23 17 00
24 18 00
25 19 00
►Allocation units (unused spaces = 0).
26 1A 00
27 IB 00
28 1C 00
29 ID 00
30 IE 00
31 IF 00
210
Soul Searching
When you execute it, this system call will look at the file name in the FCB
and, then, will go and read the disk directory to see if there’s a file name there
that matches the name in the FCB. If it finds the file, itwill write the direc
tory record that contains the name into the DMA buffer. Since there are four
directory entries in each record of the directory, there will be three other
entries written into the DMA besides the one you want. In order to tell your
program which of these four entries is the right one, Search For First returns
a number in the A-register that corresponds to the position of the desired file
in the directory record:
This system call only looks for the first occurrence of a file in the directory.
There are two reasons why a file whose name you have placed in the FCB
may have more than one directory entry. First, it may be so large that it
occupies more than one extent. Each extent that the file occupies requires an
additional directory entry. Second, you may have used wildcards in the file
name you placed in the FCB, in which case, a number of different file names
may all fit the pattern.
WILDCARDS
As you know from using C P/M , it is possible to use the characters “?”
(meaning any character) and “ *” (meaning any group of characters) in a file
name. When you do this, any filename that matches the pattern you have set
up will be operated on. For instance, if you type:
A>dir chap????.txt
all the files having a name that starts with the four letters “chap” and that
have an extension of “txt” will be listed, such as “chapter.txt”, “chap-2.txt” ,
and so on. Similarly,
A>di r * . t x t
211
Soul of CP/M®
will list all files with a file type of “ txt” . We can use one of these two conven
tions in the Search For First system call: the “?” character. The “ *” cannot be
used, because it is the CCP that translates an “ *” into a “?”, and your pro
gram doesn’t have access to the CCP.
Let’s see how this call works. Type in the following program, using DDT:
- a 1 00
0100 mvi c, 1a Set DMA address to 400.
0102 Lxi d , 40 0
0105 c a ll 5
0108 mvi c,11 Search For First.
010A lxi d,5c
010D c a ll 5
0110 mvi c,02 Print out directory code.
0112 adi 30
0114 mov e,a
0115 c a ll 5
0118 rst 7 Back to DDT.
Save the program as testl09.ddt, and then go back to D D T with the pro
gram:
A>ddt t e s t 1 0 9 . d d t
To make the program work, all you need to do is to put the name of the
“target” file (the one whose name you want to find in the directory) into the
FCB. This is easily done using the “i” command. Assume you’re going to
look for a file called “wsibm.com” :
-g100
2*0118
212
Soul Searching
-d400,47f
0400 00 45 58 41 4D 50 20 20 20 43 4F 4D 00 00 00 01 .EXAMP COM___
0410 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0420 00 45 58 41 4D 50 20 20 20 41 53 4D 00 00 00 01 .EXAMP ASM___
0430 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0440 00 57 55 49 42 4D 20 20 20 43 4F 4D 00 00 00 0C . WSIBM COM___
0450 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0460 00 42 4F 42 20 20 20 20 20 54 58 54 00 00 00 03 .BOB TXT___
0470 21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
That looks like a lot of numbers, but as you can see from the ASCII
printout on the right, four files are listed, and the one which we originally
asked for—“wsibm.com” —is in the third position down, corresponding to the
“2” that was returned as the directory code.
Let’s take one of these directory listings and examine it more closely:
Filename typ
f ..... s1 s2
0400 00 45 58 41 4D 50 20 20 20 43 4F 4D 00 00 00 01 .EXAMP COM.
0410 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0 1 2 3 4 5 6 7 8 9 A B C D E F
In the lower line, from 410 to 4 IF, are the allocation units, the same as
they are in the FCB. These are either 16 one-byte values, if the disk is single
density, or 8 two-byte values, if the disk is double density.
Now we’re going to find out how to find filenames that occur more than
once in the directory.
Soul of CP/M®
As you can see, this system call is very similar to Search For First. The
difference is that instead of starting at the beginning of the directory as
Search For First does, this call starts looking for a file name at the place
where either Search For First or Search For Next left off looking when it was
last called. This is useful in either finding files that have more than one
extent, or when the use of wildcards has made it possible for more than one
file name to match the name given in the FCB.
Let’s write a little program to look at a number of files. Take the program
from Search For First, given earlier as “test 109.ddt”, and add new code as
shown in the following program, starting at location 119:
You can use the program to search for a group of files, using wildcards.
Here’s how. First, figure out what wildcard name you want to search for.
Let’s say you want to look at the directory entry for every file that has a file
extension of ASM. Type:
- i ? ? ? ?? ?? ?. asm
That’s eight question marks, one for each character position. (As noted ear
lier, you can’t use asterisks here because BIOS doesn’t understand them; only
the CCP understands asterisks.)
Now, run the program from the beginning:
-g100
1*0118
^-----------------This “ 1” means filename found in second position.
-d400,47f
0400 00 45 58 41 4D 50 20 20 20 43 4F 4D 00 00 00 01 .EXAMP COM___
0410 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0420 00 45 58 41 4D 50 20 20 20 41 53 4D 00 00 00 01 .EXAMP ASM___
0430 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0440 00 57 55 49 42 4D 20 20 20 43 4F 4D 00 00 00 0C . WSIBM COM___
0450 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
0460 00 42 4F 42 20 20 20 20 20 54 58 54 00 00 00 03 .BOB TXT___
0470 21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
Well, it’s our old friend, the same directory record we saw before. Since it
has an ASM file in the second position, a “ 1” is printed out when we run the
program.
When we search for the next ASM file, we want to use the Search For Next
system call, so we start our program at location 119 instead of at the begin
ning.
- g 1 19
0*0118
^-----------------The “0” means file found in first position.
215
Soul of CP/M®
-d400,47f
0400 00 43 4F 55 4E 54 20 20 20 41 53 4D 00 00 00 01 .COUNT ASM-------
0410 1D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..................................
0420 00 43 4F 55 4E 54 00 00 00 43 4F 4D 00 00 00 01 .COUNT COM-------
0430 1E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...................................
0440 00 4D 45 4D 52 20 20 20 20 43 4F 4D 00 00 00 OC .MEMR COM------
0450 1 F 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .....................
0460 E5 54 45 53 54 31 30 30 20 43 4F 4D 00 00 00 02 .TEST100 COM--
0470 21 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 .....................
The “0” that our program printed out shows us that the file we’re looking for
is in the first position, and sure enough, there it is.
We can keep doing this, executing the Search For Next system call with a
-gl 19, and then dumping out the buffer to see what’s there, until we’ve found
every ASM file in the directory.
What good is all this? After all, you can use the regular D IR and STAT
commands in C P /M to find out the same information and much more conve
niently. That’s true, you can use D IR and STAT, but your program can’t.
Everything we’ve shown here can be done by your program; it can set up the
FCB with a program name, execute the “ Search” system calls, and then
examine the contents of the DMA buffer to read the directory entries. When
we get to the W ORDS program at the end of this chapter, we’ll show you an
example of just this process.
We’ve seen how to search the directory for all of the ASM files. If you want
to look at everything in the directory, all you need to do is type:
- i ????????. ???
This will match with every file, so when you execute the program four
times, it will (usually) find all four files in each record of the directory record
in the FCB.
. cp9
-g100
0*0118 First position.
- g 1 19
1*0118 Second position.
216
Soul Searching
—g 119
2*0118 Third position.
- g 119
3*01 18 Fourth position.
At any time, you can dump the contents of the DMA buffer to look at this
record of the directory. It will show the same page until it’s found the last of
the four entries; then, a new record will be loaded in:
-g100
0*0118 First position in next record.
When you see the 0, you know that a new record has been loaded in, so
you can dump it to look at all four new directory entries. Continuing this
process will eventually show you the entire directory and the directory entries
for every program on your disk. This program will, in fact, do something a
little special; it will show you not only existing files, but files that have been
erased.
ERASED FILES
Look back at the dump in the Disk Directory section of this chapter that
contained the file TESTIOO.COM. The first byte in the directory listing of
this file is E5, not 00 as it is for the other files. W hat does the magic number
E5 mean here? It means that the file has been erased, probably through the use
of C P /M ’s “era” command. When you erase a file this way, C P /M doesn’t
actually blot out the file on the disk; it doesn’t even blot out the information
about the file in the directory. All it does is set this one byte in the directory
entry to E5. Later, if it needs to go through the directory looking for some
thing, it ignores all the entries whose first byte is E5, since it knows they have
been erased. (E5 was apparently chosen for this purpose because it’s the
number that is placed in every byte of a newly formatted disk.) Of course, if
BDOS, at some point, needs the space on the disk formerly occupied by the
erased program, it will take it and, if it needs the space in the directory, it will
take that too, but until it needs this space, the erased file is still there.
This leads to an interesting idea. Suppose you accidentally erased a file;
would it be possible to get it back again, simply by changing the E5 back to
00? Sure would. Let’s see how to go about it.
217
Soul of CP/M®
1. Find the erased file in the directory and record the number of records
and the allocation units it uses.
2. Set up a new FCB using the name of the erased file and the number of
records and allocation units discovered in Step 1, above.
3. Write this FCB back into the directory by performing a “Close File”
system call.
To make this process easier, we’ll add a few lines of code to the testl lO.ddt
program that we used above.
Use STORE to create a 3-record file; you can call it BADFILE.TXT. Check
that it’s there, using TYPE2 (or C P /M ’s “ type” function). Now, erase it!
A>era b a d f i L e . t x t
Use “dir” to check to see that it’s really gone. Imagine your horror if this
were a good file, filled with irreplaceable data, that you had—in a moment of
inexcusable inattention—erased.
Actually, you already know how to perform this step. Load in testl 11.ddt
with DDT. Then, type:
- i ????????.???
to set up the FCB to look for all files and, then, repeatedly execute the pro
gram as shown in the last section. (The first part of the program works just
the same as it did before.) Every time a new record is loaded in from the
directory, dump it with “-d400,47f”, and see if BADFILE.TXT is one of the
four entries. (You can’t just put BADFILE.TXT in the FCB and search for it
because it’s been erased, and BDOS can’t “see” it anymore.) Eventually,
you’ll find the entry. Maybe it will look something like this:
-d400,47f
00 57 53 4F 56 4C 59 31 20 4F 56 52 00 00 00 18 . WS0VLY1 0VR.
29 2A 2B 00 00 00 00 00 00 00 00 00 00 00 00 00
E5 42 41 44 46 49 4C 45 20 54 58 54 00 00 00 03 .BADFILE TXT.
2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 48 45 58 49 44 45 43 20 41 53 4D 00 00 00 14 .HEXIDEC ASM.
1 F 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 4C 4F 41 44 20 20 20 20 43 4F 4D 00 00 00 0E .LOAD COM.
21 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-ib a d fi Le.txt
Now, open the file, by executing the “open file” system call in our testl 1l.ddt
program:
-g124
* 0 1 2C
The reason for opening the file is that we’re going to close it, and you can’t
close an unopened file.
Now, put the information you found from the old directory listing into the
FCB:
- s6 a
006A 00 0 -------- -------------- -------------- This byte must be zero.
006B 00 3 -------- -------------- -------------- Number of records.
006C 00 2C -------- -------------- -------------- Allocation unit.
006D 00 . -------------- -------------- -------------- More allocation units would go here.
The FCB now contains all the information that we need to put in the direc
tory listing.
By closing the file whose name is in the FCB, we write all the information
in the FCB into the directory on the disk. Execute the “close file” section of
the program:
220
Soul Searching
- g 1 2D
*0135
That’s it!
Is It Really Back?
Go back to C P /M and use “dir” to verify that the file has been restored. Is
it back? If so, bravo! If not, you probably made a typo somewhere. Try it
again.
Now, before you do any writes to the disk, do a warm boot (control-c).
Why do we need to do this? The answer lies in a mysterious place called the
“bit map.”
Ask yourself this question: when BDOS starts to write a record to the disk,
how does it know which allocation units are already in use and which are
available to be written in? One way it could find out is to read the directory
and make a list of all the allocation units shown in the entries of active files
(“active” meaning not erased). Then it would know not to write to these
units. However, scanning the directory is a lengthy process involving a disk
access, and it is not something that BDOS would want to do every single time
it writes a record. So, instead of scanning the directory every time it wants to
do a write, BDOS only scans it once: when the disk is first initialized (by a
warm boot).
BDOS uses an area of high memory for something known as a “bit m ap”
(sometimes called the “ allocation vector”). On a single-density disk, this is
simply a string of 242 bits (31 bytes). Each bit represents one of the alloca
tion units on the disk. If the allocation unit is in use, then the bit is set to 1. If
it is free, the bit is set to 0. When a warm boot is performed, BDOS scans the
directory and creates a bit map based on the information it finds there. Then,
every time it needs to do a “write,” it looks at the bit map to see what alloca
tion unit is free, rather than going back and looking at the directory. As it
writes, it will set the bits that correspond to the allocation units it has written
into to a 1, to show they have been used. When you erase a file with “era,”
the bits used by the file are set back to 0, to show that the allocation units,
which they represent, are again available for use.
221
Soul of CP/M®
Here’s how the bit map might look if most of the allocation units on the
disk were in use:
If we erase a file, it will free whatever allocation units the file was written
into, thus setting the corresponding bits to 0:
(Note that, as discussed above, the allocation units of a particular file aren’t
necessarily contiguous.)
Now it becomes clear why you need to do a warm boot after you have
rescued the erased file. Suppose you rescued the erased file and, then, without
doing a warm boot, caused BDOS to write something to the disk. It would
look in the bit map and, finding that the allocation units of the erased file
were free, it would write over them (if it needed the space). Goodbye file! To
avoid this, we simply cause the bit map to be recreated, using the new infor
mation we’ve put in the directory with our rescue process, by doing a warm
boot (or a cold one, for that matter). Our file will then be completely legiti
mate in all respects and will be safe from post-rescue disaster.
Here’s what the program does. You type in a file name, containing wild
cards if you wish. The program then finds all the files that match the name
you typed in, and it counts the number of words in each one. These word
counts are then printed out next to the name of each program, like this:
A>words * .asm
F ile s Words
In this example, we’ve counted the words in all the ASM files on the disk,
but you could just as easily count all the TXT files, or all the files whose
names start with “chap”, or whatever.
W ORDS can be a useful program for anyone doing serious writing, since
managers and editors always seem to ask, “Well, how many words are there
in that report, anyway?” With WORDS, it takes only a few seconds to find
the length of that 50-page short story for which the New Yorker says it is
going to pay you by the word.
How does the program count words? Essentially, by counting spaces.
However, this is not quite as simple as it sounds, since a string of spaces must
only be counted as one space, and carriage returns and linefeeds should also
be counted as spaces, since they separate words too. Also, the program
should avoid getting confused when words fall across the boundary between
one record and the next.
But before we get into the actual word-counting process, let’s look at how
the program deals with wildcards.
You learned in the last section how the Search For First and Search For
Next system calls handled wildcards. If you put a file name which contains
question marks in the FCB, these calls will find the directory entries for all
the files that match the pattern. This works very well if all you’re doing is
looking at the directory entries, as we had to do to save the erased file. But
suppose you want to actually read the files that you find using this process?
Then there’s trouble. Why? Because when you “open” a file (which you must
Soul of CP/M®
do to read it), the Search For Next system call loses track of where it is in the
directory. If you use Search For Next, and then Open File, and then Search
For Next again, there will be error messages as Search For Next tries desper
ately to figure out why it has lost its place.
The solution to this problem is to do all the searching first and, when it’s
done, do all the reading. So, we first scan through the directory, looking for
filenames that match the name in the FCB. When we find them, we put the
names in an array called “ table” in our program. Once we’ve found all the
names that match, we go back, take them out of “ table” one at a time, open
the files, read them, count the words, and print out the totals.
Take a look at the program in Listing 7-1. D on’t be alarmed; it may be big.
but it’s a pussycat. We’ll take you through it section by section, until it all
makes sense.
We’ll need two flowcharts to describe the program in Listing 7-1. One will
deal with the entire W ORDS program (Fig. 7-4), and the other will cover the
major subroutine called W COUNT (Fig. 7-5), which actually counts the
words in a file. Let’s begin our detailed examination of the program (Listing
7-1) by thinking for a moment about the stack.
Stack Management
; s e t number o f names i n a r r a y
+■»
o
01OA AF xra a ; p u t s 0 i n A -r eg
01 OB 32E202 st a n mcntr
r
225
Soul of CP/M®
226
Soul Searching
c
o
014F EB xchg ;save t a b le p o i n t e r
0150 220703 sh Ld pntrtab
MOV E A F I L E NA ME I N T 0 FCB
; c l e a r c u r r e n t r e c o r d number and e x t e n t i n
; f c b b e f o r e t r y i n g t o open f o r c oun t
/
017D AF xra a ; p u t 0 i n A - re g
227
Soul of CP/M®
;C 0 U N T N U M B E R OF WOR D S I N FILE
228
Soul Searching
229
Soul of CP/M®
T—
s ub cn t mvi ; c h o l d s ASCII co un t
o
1
s
; check f o r l e a d i n g 0
r
024D FE31 cpi ' 11 ; l e s s t ha n 1 ?
024F D25B02 j nc n oz er ;nope
0252 78 mov a,b ;check f o r 0 fla g
0253 B7 ora a ; i s i t set
0254 79 mov a,c ; p u t c back i n a
0255 C8 rz ; s k ip le a din g 0
0256 59 mov e,c
0257 CD6C02 ca 11 condi s ; p r i n t i t (zero)
025A C9 ret
025B 06FF n ozer mvi b ,0 ffh ; s e t 0 f l a g i n b f o r nonze
025D 59 mov e,c
230
Soul Searching
;ourprn subroutine
; d i s p l a y s a s t r i n g , u s i n g c onout
; H L = s t a r t a dd re ss o f s t r i n g , C=l en gt h
/
0262 5E ourprn mov e,m ;g e t character
0263 CD6C02 ca 11 condi s ;p rin tit
0266 23 i nx h ; i ncrement p o i n t e r
0267 0D dcr c ; d e c r e m e n t c ount
0268 C26202 jn z ourprn ; done y e t ?
026B C9 ret ; yes
F
; c o n d i s s u b r o u t i n e - d i s p l a y s one ch ar u s i n g c onout
; e x p e c t s e t o c o n t a i n t h e char
F
026C E5D5C5 condi s push h ! push d ! push b
026F 0E02CD0500 mvi c . c o n o u t ! ca 11 bdos
0274 C1D1E1 pop b! pop d ! pop h
0277 C9 ret
F
; c r l f s u b r o u t i n e - p r i n t s a r e t u r n and l i n e f e e d
02CB 2D2D2D2D2D db
0
£_
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
N
V
231
Soul of CP/M®
Print “No
Search for first
file found”.
occurrence of
filename.
“last file"
Count words
in file (“wcount”
Put filename
in array.
“alldone”
Fig. 7-4. Flowchart of the “ WORDS” portion of the program in Listing 7-1.
232
Soul Searching
Display filename
and equal sign.
T
Set status flag to "spaces".
T
Open file.
No Print ‘No
file found” .
Set wordcount = 1.
"lo op" j
Print wordcount
Set pointer to start of (In decimal)
record in DMA. using BINIDEC.
(
Get one character Exit from A
from DMA. wcount J
Is it an end-of-file?
j T no
T no
"next"
Is it the continuation ■«^Y es
Increment
of a word? pointer to DMA
to next character.
|N o
Increment
word count.
ł
Set status flag
to "word". No Done all 128
characters in DMA
Fig. 7-5. Flow chart of the “ W C O U NT” su b ro u tin e in the ‘ ‘WORDS program
(Listing 7-1).
233
Soul of CP/M®
ever data it needs) expand upward, while they start the stack in high mem
ory and have it expand downward. This keeps a “ no-m an’s-land” free
between the program and the bottom of the stack (until, if you’re not care
ful, either the program or the stack gets too big, and they run into each
other, and the system crashes).
What do we mean by stack management? Well, so far, whether you’ve been
aware of it or not, we’ve let various other programs take care of the stack for
us. When we used DDT, it started the stack at location FF (which turned out
to be slightly inconvenient for us, since that’s where the default DMA
address is). When we ran programs in the form of COM files, we let C P/M
take care of the stack.
W hat’s wrong with that? Well, suppose we had made a mistake in our pro
gram, like putting more things on the stack than we took off (too many
...
Top of memory.
CP/M
free
memory
/
stack ------------ User’s stack, growing downward.
llllllllllllllllllllll
-4 ------------ No-man’s-land.
llllllllllllllllllllll
Assembly
----------- User’s program.
language
program 100
/
Zero page 0
/
Fig. 7-6. Location ot the u ser’s program and the stack.
PUSFles and not enough POPs). If we did that, then when we went back to
D D T or C P/M , or whatever was managing the stack for us, there would be
trouble because what it (D DT or C P/M ) was taking off the stack wouldn’t be
what it thought there should be. Or, suppose we just put too many things on
the stack for D DT or C P /M to handle. Then, we’d probably overwrite that
part of their program just under the stack, and bad trouble would result.
So, the moral of all this is to set up your own stack in your program and
make it big enough to handle everything you ever want to put in it: two bytes
for every PUSH, and two bytes for every call to a subroutine. When you start
your program, save the old stack pointer, and when you exit from your pro
234
Soul Searching
gram, restore the stack pointer with this old value. This guarantees that
C P /M or D D T will find the stack just as it was when they passed control to
your program, and they’ll be happy.
Several of the instructions that work on other 16-bit registers also work
on the SP-register. For instance, “lxi sp,14ffh” will put the constant 14FF
in the SP-register, and “ dad sp” will add the contents of the stack pointer to
the HL-register. However, to load something into the stack pointer, we
need another instruction, which is “sphl” . As its name implies, this instruc
tion takes the 16-bit value from the HL-register, and puts it in the stack
pointer.
Before SPHL is executed:
SP-register HL-register
/
BF00
SP-register HL-register
✓ y Z ■ ■ A
BF00 /-* — BF00 /
Examples:
sphl
In the W ORDS program, we set up the local stack at the beginning of the
progam (locations 100 to 107), by adding the contents of the stack pointer
and storing them in “oldstack”, and then putting the address of the “top” of
our own stack (“stktop”) into the stack pointer with an lxi instruction. Later,
when we’re about to exit from our program (at location “alldone”), we
restore the old stack pointer by getting it into the HL-register and transfer
ring it into the stack pointer with an “sphl” instruction.
This is good programming practice and should be followed in all but the
smallest programs. D on’t say we didn’t warn you.
235
Soul of CP/M®
Data to be Data is to be
transferred transferred
is in this section to this section
of memory. of memory.
1000 /I 20DA
41 41
/ / 20DB
1001
53
53
/ 1002
y\ 20DC
68 68
/ 1003
/ 20DD
HL-register DE-register
points to 7F points to 7F
byte in this / 1004 byte in this / 20DE
part of part of
memory. — 8B
/ 1005
/ 20DF
9D
/ 1006
/ 20 E0
AO
/ /
Fig. 7-8 is a pictorial idea of how the data is arranged in memory. At the
point shown in the diagram, all the data bytes down to 7F have been trans
ferred.
Since all these instructions (except the “jn z repeat”) are only one byte, the
routine is short and executes quickly. Of course, the correct values of the
236
Soul Searching
pointers to the source and destination blocks must be placed in HL and DE,
and the correct value of the count must be placed in the B-register, before the
“repeat” loop is executed.
You’re already familiar with the instructions that transfer 8-bit data to and
from memory using the HL-register as a pointer. They’re “mov r,m” and
“mov m,r”, where “r” stands for any of the 8-bit registers (a, b, c, d, and e),
and “m ” stands for the memory location contained in HL. Will this instruc
tion work with other registers besides HL? No, it won’t—that’s not something
the 8080 likes to do. In order to use the DE or BC registers as pointers to
memory, we have to use several other more limited instructions: “ stax” and
“ldax.” The limitation is that these instructions can transfer data only
between memory and the A-register, rather than between memory and all the
8-bit registers as “mov” can. Otherwise, they’re quite similar.
The “ldax r” instruction loads the A-register with the byte in memory at
the address pointed to by register “r”, where “r” stands for either the BC or
the DE register, but not the HL register. (Of course, you will use 1-letter
abbreviations for the registers: “b ” for the BC-register, and “d” for the DE-
register.)
Examples:
ldax b
ldax d
Similarly, “stax r” stores the contents of the A-register into memory at the
location contained in register “r”, where “r” can be either the DE- or the BC-
register.
Examples:
stax b
stax d
237
Soul of CP/M®
DE-register
/ / 0100 Section of
memory
2011 containing
/ 0101 program.
A-register
/ 0102
/ 0103 LDAX D
1A
/ 0104
/
llilllllllllllllllll
llllllllllllllllllll
/ 2010 Section of
memory where
/ 2011
constant is
stored.
6B
/
2012
/
A fter LDAX D is executed:
DE-register
/ / 0100
Section of
memory
containing
/ 0101 program.
/ 0102
/ 0103 ■LDAX D
12
/ 0104
/
/
//////////////////
llllllllllllllllll
/ / Section of
2010 memory where
constant is
/
2011 stored.
6B
/
2012
238
Soul Searching
BC-register
A-register
/ 0101
containing program.
/ 0102
FA
/ 0103 STAX B
10
/ 0104
/
/
////////////////////
////////////////////
/ 2010 Section of memory
where constant is
/ 2011 stored.
2012
/
After STAX B is executed:
BC-register
Section of memory
containing program.
STAX B
Section of memory
where constant is
stored.
239
Soul of CP/M®
It’s easy to take a 16-bit value from a memory register and put it in the
HL-register; we use a “lhld” instruction. U nfortunately, there is no such
instruction which will load a value from a memory location into the DE-
register. If we knew when we were writing the program what value we
wanted to load in, we could use an “lxi” instruction, but we don’t, since this
value changes every time we load in a new file name. So how do we get a
value from memory into the DE-register? We load it into the HL-register
first, with an “lhld,” and then exchange the values of the HL and D E regis
ters with the “xchg” instruction. This instruction is used again, ju st after
the repeat loop, to save the contents of the DE-register in memory location
“pntrtab.”
HL-register DE-register
HL-register DE-register
As we described earlier, the program first looks through the disk directory
for all of the program names that match the name in the FCB. These names
are then stored in an array, called “table.” This is accomplished in the section
240
Soul Searching
of the program called “Scan For File Names and Put in Array.” As you can
see, the code to do this doesn’t look too different from the simple D D T pro
gram that we used to perform the same search “by hand” in the last section.
The actual transfer of the names to the array takes place in the subroutine
“putary,” and makes use of the little memory-to-memory routine described
earlier. After putting the file name in “table,” this part of the program then
increments “nmcntr,” which holds the number of file names, and, then, goes
back to look for another possible match.
Once all the file names have been transferred to “table,” the program then
goes on to its second phase, that of reading the individual files one at a time
and counting the number of words in each one. This is done in a short section
of code called “Count Number of Words in File.” All this section does is
keep track of how many files have been counted, using the “nm cntr” varia
ble. Once they’ve all been counted, it restores the old stack pointer and
returns to C P /M in “alldone.”
The real work of counting the number of words in each file is performed by
the subroutine “wcount.” This subroutine starts by displaying the file name
and opening the file. The rest of the routine consists mostly of two nested
loops: (1) the outer one, “loop,” reads a new record every time it is entered,
while (2) the inner one, “loop2,” examines a new character each time it is
entered.
We use a 1-byte variable called “status” to keep track of whether we’re in
the middle of a word, in which case “status” is set to 0, or in the middle of a
group of spaces, in which case “status” is set to 1.
Depending on what the character is that we’ve read, and what “status” is
set to, we do different things. If the character is a space, or a carriage return,
or a linefeed, then “status” is set to a 1. If the character is not any of these
things, then we assume it’s part of a word and either one of two things may
happen. If “status” is a 0 (meaning that the last character was a nonspace
character and, thus, we’re already in the middle of a word), no action is
taken. If, on the other hand, “status” is set to a 1 (meaning that the last
character was a space), then, first, the word count is incremented, and sec
ond, “ status” is set back to 0. Thus, every time we change from a space to a
nonspace character, we count one word.
Subroutines in W O RDS
If you played with the BINIDEC routine earlier in the book, you’ll notice
that it’s somewhat different here. We’ve added a refinement; it no longer
prints out leading zeros. It accomplishes this by using the B-register as a
241
Soul of CP/M®
status flag to keep track of whether or not a nonzero character has been
encountered in the process of printing the decimal number. If so, then the B-
register is set to 1, otherwise it’s 0. If the routine is about to print a zero, it
first checks to see if the B-register is 0. If so, it doesn’t print anything, but
goes on to calculate the next digit.
The “ourprn” subroutine simply prints out a string of characters on the
string, starting with the character at the address in the HL-register. This is
useful for printing out the names of the files; they aren’t terminated with
dollar signs, so we can’t use the Print String system call.
The “condis” and “crlf” subroutines should be self-explanatory; in the
interest of making them look more compact, they have been written with a
lot of ! signs.
That about takes care of our description of the W ORDS program, which is
the longest we’re going to try to cover in this book. If it isn’t all clear to you
right away, keep working on small sections of the program. One of the best
ways to try to understand a long program is to sit down and imagine how you
would try to write a particular section of it yourself. Then, compare your
code with the program. They may be surprisingly similar, in which case,
you’ll immediately understand what that part of the program is supposed to
do. And, if they don’t look the same, then, at least you’ll have defined the
problem to yourself and, in comparing the differences, you’ll learn something
new about programming.
In the next chapter, we’re going to turn to something different: how to
interface assembly language routines with higher-level language programs.
242
CHAPTER 8
Teamwork
Using Systems Calls From BASIC
Besides being called from assembly language routines, the system calls that
we’ve described in the preceding chapters can also be called from higher-level
languages such as BASIC, Pascal, and FORTRAN. (They can even be called
from programs such as dBASE II™, a data-base program with a built-in pro
cedure for interfacing with machine-language routines.) When called in this
way from higher-level languages, the system calls are usually part of a short
assembly language routine which performs a specific function that is difficult
or time-consuming to accomplish in the higher-level language.
This chapter describes how to to call assembly-language routines from
within a BASIC program. Although BASIC is used in the examples, many of
the techniques described are applicable to other high-level languages as well.
There are four main questions that must be answered in order to connect
an assembly language routine to a BASIC program.
These are:
243
Soul of CP/M®
We’re going to start off by describing the four questions that are raised
above and, then, we will use an example program to illustrate one possible set
of answers (the simplest one). D on’t worry if all the details don’t seem com
pletely clear while you’re reading about the four problems. Things will
straighten themselves out when we get to the example. In fact, you might
want to glance forward at the example program (which is called BINIHEX2)
from time to time as you’re reading about the four problems—just to keep
yourself grounded in reality.
Once we’ve everything working in the simplest possible way, we’ll go back
and, using different approaches and examples, explore some of the more
complicated alternative solutions to the problems. Before we start, let’s agree
on a convention: “assembly language” will be abbreviated to “A-L” in the
rest of this chapter. This will save a lot of writing for us, and a lot of eyestrain
for you.
grams go wherever the BASIC interpreter puts them, growing upward from
itself. Fig. 8-1 shows what that looks like with an A-L program.
The CCP need not be in memory when a program is running, so it can be
overwritten. That is, A-L programs may extend all the way up to the bottom
of BDOS, an address called FBASE. We’ll say more about FBASE later.
The BASIC interpreter always uses the CCP area. Fig. 8-2 shows how
memory looks when BASIC is loaded.
Notice how the user’s program grows upward into free memory, while the
BASIC stack grows downward to meet it. This means that we never know
exactly where the space in between the two is going to be. It can get gobbled
up by the program or by the stack and only the BASIC interpreter knows
when that’s going to happen. So this area doesn’t look like a good place to try
to put an A-L routine. Where else could it go?
The way BASIC handles this is by providing a way to “protect” a section
of memory above the stack. This is usually accomplished when BASIC is first
loaded, by specifying a memory address in the load command string. For
instance, if we loaded our particular BASIC interpreter, MBASIC5, with the
following command,
A>mbasic5 / m:a000
then, mbasic would start its stack at A000 hex, and would let it grow down
ward from there. Thus, the memory between A000 and FBASE (the bottom
of BDOS) would be available for our A-L program.
Let's see how this “protected” memory looks. It is illustrated by the dia
gram in Fig. 8-3. So if we put our A-L program at A000 hex, we know it will
be safe and happy, provided, of course, that there is enough room for it
between A000 and the bottom of BDOS (FBASE).
Anyway, that is the way things should be. The way they really are, however,
is more complicated. This is because (at least, in this first and simplest
approach) we have to load our A-L routine using D D T and, thus, it must fi t
below DDT.
Why do we have to load it using DDT? That’s a long story and we’ll get to
it in the next section. For the time being, let’s see what memory looks like
with D D T loaded. This is shown in Fig. 8-4.
The question, then, is, where is the bottom of DDT? If we know this
address, which we’ll call DBASE, then we can assemble our A-L program so
that it lies just below it.
Finding out exactly where DBASE is may not be easy. However, the fol
lowing table shows the approximate addresses for various sized systems.
245
Soul of CP/M®
z: Top of memory.
BIOS
/
BDOS
FBASE
• Can be over-written
CCP if more space is
CBASE
needed.
//////////////////
User’s
A-L
Program 0100 hex
0000 hex
Zero Page
BDOS FBASE
/
Stack
//////////////////
//////////////////
User’s
BASIC
Program
/
BASIC
Interpreter 0100 hex
z: Top of memory.
BIOS
BDOS
Stack
Address specified
////////////////// in BASIC load command.
//////////////////
User’s
BASIC
Program
/
BASIC
Interpreter 0100 hex
Fig. 8-3. A “ protected” memory area for the assembly language subroutine.
246
Teamwork
Top of memory.
BIOS
/
BDOS FBASE
/
DDT DBASE •* ------ W hat’s this address?
/
A-L Routine
/
Stack
llllllllllllllllll
llllllllllllllllll
User’s
BASIC
Program y
BASIC
Interpreter 0100 hex
Zero Page 0000 hex
There’s another way to figure out where DBASE is. Load DDT, and use it
to look at itself! You should begin by turning off your system and then pow
ering it up again, to ensure that all unused memory is filled with zeros. Then,
load D DT and use the “d” command to look in high memory, somewhere
around the addresses given in the preceding table. Below a certain point,
memory will be filled with all zeros (or sometimes FFs, depending on your
system). Above that point, it will be filled with the hex code of the D DT
program. You should be able to find this point by trial and error. When you
do, you’ve found DBASE.
So now, you know where to put your A-L routine in memory: just below
the address that you’ve found for DBASE. Or even a good bit below, if mem
ory space is not critical. (It won’t be in our short demonstration programs.)
There is, of course, a problem with this approach. Since we are using
D D T to load the A-L program, we have to put it lower in memory than we
247
Soul of CP/M®
want to. Although this isn’t a problem with our example programs, it could
certainly become an inconvenience if we were using a very long BASIC pro
gram that needed all the space it could get. Normally, we can load pro
grams all the way up to FBASE (the bottom of BDOS), but now, we can
only load as far as DBASE, the bottom of DDT. Later, we’ll look at ways to
avoid this problem.
Now, we can go on and load BASIC, with the proper memory limit, and
we’re on our way. We’ll see exactly how this is done when we get to our
example program.
There are two parts to this question: getting from BASIC to the A-L rou
tine, and getting back.
248
Teamwork
We tell BASIC where the A-L routine is located in memory with the
D EFUSR statement:
This statement appears only once, at the beginning of the program. From
it, BASIC knows that every time the routine “USR1” is called, BASIC should
transfer control to location A000. (The “&H” specifies that the address which
follows will be in hexadecimal.)
To actually go to this routine, we execute a BASIC statement like:
50 D=USR1( A)
This statement could take a wide variety of forms. In fact, whenever “USR1”
appears in the program, BASIC will transfer control to the A-L routine at
A000.
Going this way is much easier. All we have to do in the A-L routine is a
“ret” and, presto, we’re back in BASIC.
B = USRKA)
The variable A is the argument that is passed from BASIC to the A-L
routine. From the viewpoint of the BASIC program, passing the value of the
variable A is automatic. When this statement is executed, the BASIC inter
preter makes sure that this value is passed to the A-L routine.
The variable B is the argument that is returned to BASIC from the A-L
routine. Again, from the viewpoint of the BASIC program, the procedure is
249
Soul of CP/M®
automatic. Once the statement has been executed, B will have whatever value
was returned by the A-L routine, and BASIC can make use of the value in
subsequent statements. So it’s fairly straightforward to deal with these argu
ments in the BASIC program. But what does the A-L routine have to do first,
to find out what the value of A is and, second, to pass the value of B back to
BASIC?
BASIC keeps a set of memory locations deep in its innards that are called
the “ Floating Point Accumulator,” or FAC. These locations are used to pass
arguments back and forth between BASIC and the A-L routine. Either 2, 4,
or 8 of these locations are used to hold the argument, depending on its varia
ble type. For integer variables, which are two bytes long in BASIC, two of the
locations are used, as shown in Fig. 8-5:
If we assume that the BASIC variable “A” is an integer, then, when the
statement “USR1(A)” is executed in the BASIC program, BASIC will auto
matically put the the value of A into the FAC. When control passes from
BASIC to the A-L routine, BASIC makes sure that the HL-register contains
the address of FAC-3. Thus, at the beginning of the A-L routine, all you have
to do is get the variables out of the FAC using the address of FAC-3 in the
HL-register.
An added nuance here, which we won’t make use of but which could come
in handy in some situations, is that the A-register holds a number which tells
you what kind of variable is in the FAC:
2 = integer
3 = string
4 = single-precision floating point
8 = double-precision floating point
FAC
z 71 higher
memory
FAC-1
250
Teamwork
So, if your A-L routine doesn’t know what kind of variable to expect, it can
find out by looking at the A-register. (Note that these numbers also tell the
number of bytes in the variable.)
In the example that follows, we’re going to describe how integers are
passed to the A-L routine and, later in the chapter, we’ll show how strings are
communicated. We won’t cover floating point variables since these can vary
from one version of BASIC to another, but the principles involved are much
the same.
How do we pass a variable to BASIC when we return from the A-L rou
tine? The same way: we put it into the FAC. There’s no need to put anything
in the HL-register since BASIC already knows where the FAC is. The value
returned by the A-L routine should be of the same type (integer, string, etc.)
as the value that was passed to it.
You should recognize the name of this program; you’ve seen it before in
the chapter on console system calls. We’re going to take this same program
and modify it to work with BASIC. That is, we’ll give BASIC the responsibil
ity for getting the decimal number from the user and converting it to binary
(it does this automatically). All that our A-L routine will have to do is print
out the binary number in hex digits.
This is actually a handy little routine since many versions of BASIC don’t
have a way to print out variables in hex format. Here’s the BASIC program:
10 DEFINT A-Z
20 DEF USR1=&HA000
30 INPUT"deci ma I n um be r "; A
40 PRINT"hex e q u i v a l e n t i s :
50 D=USR1( A)
60 PRINT: PRINT
70 GOTO 30
We’ll assume that you’re familiar enough with BASIC to follow what’s
happening here.
We define the entry address of our USR1 routine to be A000 because we
know this is well below the bottom of D D T (DBASE), which is at B400 in
our 56K system. (This will be different on different-sized systems. See the
preceding discussion on where to put the A-L routine.) We could have placed
251
Soul of CP/M®
it closer to DBASE, but there’s plenty of room in memory for these short
example programs, so we don’t have to be too precise. With larger programs,
it would be wise to assemble the A-L routine first, examine the PRN file to
see how long it is, and then ORG it so that the end of the A-L routine fits just
below DBASE.
For simplicity, we define all variables in the BASIC program to be inte
gers, using the D E FIN T statement. The integer variable A is passed to the
USR1 routine, which is the BINIHEX2 program (Listing 8-1).
; B I N I H E X 2 - C o n v e r t s b i n a r y t o h ex , p r i n t s i t o u t
; ( t o be c a l l e d f rom BASIC pro gr am)
c o n v e r t b i n a r y t o hex and p r i n t i t o u t
(on e n t r y h i h o l d s t h e b i n a r y i n t e g e r )
A004 7C mov a , h ;p u t h in a ( f i r s t d ig it)
A005 CD15A0 c a ll p r in tl ; p r in t le ft-h a n d d ig it
A008 7C mov a ,h ; p u t h i n a ( second d ig it)
A009 CD19A0 c a ll p rin t2 ; p r in t right-hand d ig it
AOOC 7D mov a , I ;p u t I in a ( t h i r d d ig it)
AOOD CD15A0 c a ll p r in tl ; p r in t le ft-h a n d d ig it
A010 7D mov a , I ;p u t I in a (fo u r th d ig it)
A011 CD19A0 c a llp rin t2 ; p r in t right-hand d ig it
A014 C9 ret ; e x i t binihex
print contents of a
252
Teamwork
A031 end
We O RG the program just where we want it to go, at A000. The first thing
that the program does is get the value of the integer A out of the FAC. Since
HL is pointing to FAC-3, the “mov e,m” instruction will put the low-order
(least significant) byte of A into the E-register. Incrementing HL causes it to
point next to FAC-2, so “mov d,m” will put the high-order byte of A into the
D-register. The complete integer is now in DE, and we swap it into HL with
the “xchg” instruction so that we can process it in the way we did before in
BINIHEX.
After printing out the four hex digits from HL, we return to BASIC with a
“ret” instruction. In this case, there is no argument passed back to BASIC.
The “ D ” in the expression D = USR1(A) in line 50 is a “dummy” argument,
meaning that it’s not used but must be there for the sake of form.
W ant to try all this out? Here’s how to do it. You should have a disk that
contains the BASIC interpreter, the ASM assembler, your text editor, and
DDT.
3. Assemble it by typing:
A > a sm binihex2
A > d d t binihex2.hex
Ok (BASIC prompt)
load “ BINTEST” (or “BINTEST.BAS”)
You should be able convert from decimal to hex just as you did in the
DECIHEX program.
The BINIHEX2 routine can be used in any BASIC program you like.
Y ou’ll probably be able to think of all sorts of situations where it might come
in handy. You could, for instance, explore some of the inner workings of
BASIC by using BINIHEX2 to print out the addresses of variables, and then
using PEEKs and BINIHEX2 to print out their contents in hex. (For more
on this, see the VARPTR function in your BASIC book.)
Now that you have an idea of what’s involved in linking an assembly lan
guage routine to BASIC, we’re going to go back and explore some alternative
ways of doing things.
Maybe you think it’s not elegant to use D D T to load the A-L routine, or
maybe you don’t want to keep D D T on your disk ju st to load a small A-L
routine to work with BASIC. Is there a way to get the A-L routine into mem
ory without using DDT? Yes, more than one. They each have their draw
backs.
254
Teamwork
Here’s a way that doesn’t work very well, although it sounds reasonable
enough at first.
The idea is this. Since LOAD only works with programs that start at 100
hex, why not simply ORG our routine at lOOh and then start the program off
with “filler” —a “ds” directive that will insert enough bytes to move the pro
gram up to where we want it to be. For instance, if we have to O RG it at
lOOh, but we really want it to be at aOOO, we can change the program like
this:
****************************************************
B I NI HEX 2- Co nv er ts b i n a r y t o h e x, p r i n t s i t o u t
( t o be c a l l e d f rom BASIC pro gr am)
0100 ds 9fOOh
9 f0 0h ; 10Oh + 9f 00 h = aOOOh
; t r a n s f e r i n t e g e r f ro m FAC t o h i r e g i s t e r
; (on e n t r y h i h o l d s p o i n t e r t o l o - o r d e r b y t e i n FAC)
A000 5E mov e,m ; p u t lo -b y te in e
A001 23 i nx h ;bump t he p o i n t e r
A002 56 mov d,m ;p u t h i-b y te in d
A003 EB xchg ; p u t de i n h I
Well, that looks just fine. We can assemble the program with ASM, and
that’s fine too. However, when we use LOAD, we get our first clue that all is
not well.
255
Soul of CP/M®
A>load b i n i h e x 2
Let’s think about another way to get the A-L routine where we want it.
Why can’t we assemble it at 100 hex, like any other program, and then just
take all the bytes of the program and move them up to high memory where
we want them? We could use the code segment, which we discussed in the
last chapter, for moving the contents of a block of memory to another loca
tion.
Sounds good. The trouble is that if you move a program to a different
place in memory, all of the “memory reference” instructions in the program
will be wrong. Memory reference instructions are those that refer to partic
ular addresses (like “jm p ” and “ call”) which jum p to a specific memory
location. For instance, in BINIHEX2, the instruction at location A005
which is “call p rin tl”) should be CD15A0, since it wants to jum p to a sub
routine at location A015. But, if we assemble BINIHEX2 at lOOh, this
instruction will become CD 1501, which is a “call” to a subroutine at loca
tion 0115, not A015.
W hat we need, then, is a way to fool the assembler into thinking that
things are up in high memory when, in fact, they’re just down in the program,
which starts at lOOh. This way, when we move the program up to high mem
ory, the instructions will say the right thing (“call p rin tl” will be CD 15A0,
and so on).
So, how do we fool the assembler? Very cleverly! But to understand it, you
need to know what the dollar-sign symbol means to the assembler.
256
Teamwork
The Label
When the “$” symbol is used in the address field of an instruction, it takes
on the value of the current program line. For example, suppose you have the
following section of code:
; en d o f main p a r t o f program
$
All this shows is a program fragment that calls a subroutine and the begin
ning of the subroutine, “print” . Nothing unusual here. Now, let’s write it
slightly differently.
; s t a r t of subroutine p r i n t
/
0115 p rin t equ $
0115 7 C mov a ,h
257
Soul of CP/M®
By using the $ symbol and an EQU directive, we’ve given “print” the value
0015, because that is the memory location at this point in the program. Sub
sequent “CALLs” to print will go to location 0015, just as they would have if
it were simply used to label the line as in the first code fragment.
So how can we use the dollar sign? Well, we can now modify the value that
the assembler gives to a label; whereas before, it could only have the value of
the line it was on. And this suggests a solution to our problem; that is, a way to
have instructions occupy one place in memory while they refer to some place
else. Since the label “print” is given its value with an EQU directive, we can
change that value by doing arithmetic on the operand field that contains the
“$”. For instance, we could change the “equ” line in the above example to:
; en d o f main p a r t o f program
f
0111 CD1503 c a ll p rin t
0114 C9 ret
A
; s t a r t of subroutine p r i n t
f
0315 p rin t equ $ + 200h
0115 7 C mov a ,h
This would give “print” the value of 0315h, or $ + 200h. And look at this:
the “call print” instruction changes so as to refer to address 0315, even
though the the “print” subroutine is still located at 0115. (Remember that the
high and low bytes appear in reverse order in 8080 language, so that CD 1503
means a “jm p ” to 0315.)
In our situation, we need to move our routine from some place around
lOOh, in low memory, to some place up around AOOOh (or whatever address is
appropriate to your system) in high memory. To do this, we need to come up
with a number that’s the difference between lOOh and AOOOh. Actually, we
don’t need to do the calculation. We can let the assembler do it for us.
258
Teamwork
; e n d o f main p a r t o f program
f
0111 CD15A0 c a ll p rin t
0114 C9 ret
f
; s t a r t of subroutine p r i n t
AOOOh minus lOOh is 9F00, and adding 115 gives A015h, which is just
where we want “print” to go when we move the program to high memory.
To summarize: we’re going to do two things. First, we will assemble and
load our program at lOOh, but move all its bytes up to where we want them in
high memory, after the program has been loaded. Second, we will change all
the labels in the program so that they will have the correct values for their
new locations in high memory.
Let’s see how BINIHEX2 looks when we make these changes. We’ll call
the result BINIHEX3 (Listing 8-2).
; B I N I H E X 3 - C o n v e r t s b i n a r y t o hex and p r i n t s i t o u t
; ( t o be c a l l e d f rom BASIC p r o g r a m ) .
259
Soul of CP/M®
0114 = s t a r t equ $
A
9EEC = o f f s e t equ d e s t i n - s t a r t
A
; t r a n s f e r i n t e g e r from FAC t o h i r e g i s t e r
; (on e n t r y h i h o l d s p o i n t e r t o l o - o r d e r b y t e i n FAC)
0114 5E mov e,m ;p u t lo -b yte in e
0115 23 i nx h ;bump t he poi n t e r
0116 56 mov d,m ;p u t h i-b y te in d
0117 EB xchg ; p u t de i n h I
W
; c o n v e r t b i n a r y t o hex and p r i n t i t o u t
; (on e n t r y h i h o l d s t h e b i n a r y i n t e g e r )
0118 7C mov a ,h ;p u t h in a ( f i r s t d i g i t )
0119 CD15A0 ca 11 p r i n t l ; p r in t le ft-h a n d d i g i t
011C 7C mov a ,h ; p u t h i n a (s eco nd d i g i t )
0 1 1 D CD19A0 cal I pr i nt2 ; p r in t right-hand d i g i t
0120 7 D mov a , I ;p u t I in a ( t h i r d d i g i t )
0121 CD15A0 c a ll p rin tl ; p r in t le ft-h a n d d i g i t
0124 7 D mov a , I ;p u t I in a (fo u r th d i g i t )
0125 CD19A0 c a l l p r i nt2 ; p r in t right-hand d i g i t
0128 C9 ret ; e x i t b i n i hex
/
; s h i f t i f n e c e s s a r y , and change t o ASCII
260
Teamwork
/ s u b r o u t in e to p r i n t c r a c t e r i n a - r e g o u t on s c re e n
The program now has two parts. The first part is the transfer routine,
whose sole jo b is to move the rest of the program up to high memory. This is
a fairly straightforward loop, which moves all the bytes between “ start” and
“ending” to the block in high memory that starts with the location “destin”
(for “ destination”). When we’re done with the transfer, we go back to C P /M
with a “ret” .
Notice how all the labels in the main part of the program have been given
new values with the “equ $ + offset” statement. If you look at the assembled
hex code, you’ll see that all the jum ps and calls go to the appropriate loca
tions in high memory.
Here’s how we use this version of the program:
A >binihex3
This will load the program into mem ory, where it will then move itself
261
Soul of CP/M®
Ok
load “BINTEST”
Unfortunately, as you will have noticed, this approach also has a glaring
defect. Because we need to use the CCP part of C P /M to load BASIC after
we have loaded the A-L routine, we can’t locate the A-L routine just under
FBASE (the bottom of BDOS) where we would like to. We must put it below
CBASE, the bottom of the CCP. This loses us about 800 hex bytes of mem
ory. Too bad.
and 7 of memory. Remember how, when you do a “call bdos”, you’re doing a
“call 5”? Well, the instruction at location 5 is a jum p to the beginning of
BDOS, so the two bytes following the C3 in location 5 are the address to be
jum ped to. The problem is, how do you examine these bytes?
The first idea that probably occurs to you is to look at them with the “d ”
instruction of DDT. Sorry, no cigar. When D D T is loaded, it changes the
address in this location so that it can intercept calls to BDOS and, thus, keep
control of the calling program. So, looking with D D T won’t help.
But, listen! The distant sound of trumpets! It’s HEX DU M P to the rescue!
The name may not sound too romantic, but the program is really somewhat
dashing. You’ll find it in Appendix B, and what it does is to let you do a
DDT-like dump on any 128-byte section of memory, without using DDT.
Type it in, assemble and load it, and fool around with it a little. It’s a useful
little program.
Now, use it to dump page 0. That is, after the program loads, type 0 and a
carriage return:
A>hexdump
0
0000 C3 03 D2 94 00 C3 '06* C4 23 AF 23 36 40 23 77 23
0010 36 01 11 80 BB D5 C1 F1 AF 32 81 00 CD 06 E0 F5
(and so on)
1. Assemble the A-L routine at the desired location in high memory (just
below BDOS).
263
Soul of CP/M®
2. Using the PRN file, write down the hex values of all the bytes in the
routine.
3. Translate these hex values into decimal values. This is necessary
because most BASIC languages only understand decimal. (If your lan
guage understands hex values that are preceded by “&h”, then you can
skip this step.)
4. Add these decimal values to your BASIC program in the form of
DATA statements and, also, add a few lines to read the data state
ments and POKE them into memory, at the same address where you
assembled the routine.
Of course, the tedious part of this is in translating the hex values to deci
mal and typing them into your BASIC program by hand. However, it’s not so
bad for short routines.
Now, we have the PRN listing (Listing 8-3) for our BINIHEX program,
assembled at location C000 hex. We’ve chosen this location because, on our
56K system, FBASE (the bottom of BDOS) is at C406. We thus have more
than 400 hex bytes to put our program in, which is plenty. Actually we could
have used an ORG of C3D5, since the routine is only 30 hex bytes long
(C3D5 + 30 = C405), but that would be cutting it a little close if we ever
wanted to add a line or two to the routine.
; t r a n s f e r i n t e g e r f rom FAC t o h i r e g i s t e r
; (on e n t r y h i h o l d s p o i n t e r t o l o - o r d e r b y t e i n FAC)
C000 5E mov e,m ;put lo-byte in e
C001 23 inx h ;bump t he p o i n t e r
C002 56 mov d,m ;p u t h i-b y te in d
264
Teamwork
C003 EB xchg ; p u t de i n h i
; c o n v e r t b i n a r y t o hex and p r i n t i t o u t
; (on e n t r y h i h o l d s t h e b i n a r y i n t e g e r )
C004 7C mov a ,h ;p u t h in a ( f i r s t d i g i t )
C005 CD15C0 ca ll p rin tl ; p r i n t le ft-h a n d d i g i t
C008 7C mov a , h ; p u t h i n a (s ec ond d i g i t )
C009 CD19C0 ca 11 p r i n t 2 ; p r in t right-hand d i g i t
COOC 7D mov a , I ;p u t I in a ( t h i r d d i g i t )
COOD CD15C0 ca 11 p r i n t l ; p r in t le ft-h a n d d i g i t
C010 7D mov a , I ;put l i n a (fo u rth d i g i t )
C011 CD19C0 ca 11 p r i n t 2 ; p r in t right-hand d i g i t
C014 C9 ret ; e x i t b i ni hex
; p r i nt contents of a
r
C015 07070707 p r i n t l r lc ! r lc ! rlc: ! r lc ;move h i g h 4 b i t s t o
C019 E60F p r i nt2 ani Of h ; g e t r i d of high 4 b i t s
C01B C630 adi 30h ; cha nge hex t o ASCII
C01D FE3A cpi 3ah ; i f more t ha n 9
C01F DA24C0 jc notbig ; ( i t ' s not)
C022 C607 adi 7h ; t he n add b i a s (10=A, e t c . )
C024 CD28C0 notbi g c a l l p cha r ; p r in t d ig i t
C027 C9 ret
F
; s u b r o u t i n«> t o p r i n t c h a r a c t e r i n a - r e g o u t o n t o si
r
C028 E5 pchar push h ; s a v e h i ( c o n o u t uses i t )
C029 5F mov e,a ; p r i n t hex d i g i t
C02A 0E02 mvi c,conout
C02C CD0500 ca ll bdos
C02F E1 pop h ; g e t h i back
C030 C9 ret
C031 end
Look at the hex values given in the second column of the PRN listing of
this routine: 5E, 23, 56, EB, and so on, down to C9 at the end. Write down
these values and, next to each one, write its decimal equivalent:
265
Soul of CP/M®
Hex Decimal
5E 94
23 35
56 86
EB 235
C9 201
10 DE FI N T A-Z
15 G O SU B 100
20 DEF U S R 1 = & H C 0 0 0
30 I N P U T " d e c i m a l n um ber "; A
40 P R I N T " h e x e q u i v a l e n t is: ";
50 D = U S R 1 (A )
60 P R I N T :PRINT
70 GOTO 30
100 FOR I=& H C 0 0 0 TO & H C 0 3 0
110 READ D
120 POKE I,D
130 N EX T I
140 RETU RN
200 DATA 9 4 , 3 5 , 8 6 , 2 3 5 , 1 2 4 , 2 0 5 , 2 1 , 1 9 2 , 1 2 4 , 2 0 5 , 2 5 , 1 9 2 , 1 2 5 , 2 0 5 , 2 1 , 1 9 2
210 DATA 1 2 5 , 2 0 5 , 2 5 , 1 9 2 , 2 0 1 , 7 , 7 , 7 , 7 , 2 3 0 , 1 5 , 1 9 8 , 4 8 , 2 5 4 , 5 8 , 2 1 8 , 3 6 , 1 9 2
220 DATA 1 9 8 , 7 , 2 0 5 , 4 0 , 1 9 2 , 2 0 1 , 2 2 9 , 9 5 , 1 4 , 2 , 2 0 5 , 5 , 0 , 2 2 5 , 2 0 1
A>mbasic5 /m:&hc000
0k
load " B I N T E S T 2 "
266
Teamwork
That’s all there is to it. Now we can type “run,” and we’ll be airborne
immediately, since the BASIC program will POKE BINIHEX4 into memory
and then jum p to it as soon as it gets to the USR1 statement.
HEX Files, and Using a BASIC Program to Load the A-L Routine
As our last method of loading an A-L routine into memory, we’ll make use
of a special BASIC program that can directly load a HEX file:
“ HEXLOAD.BAS” .
In order to understand what the program does, you need to know some
thing about the format of HEX files. HEX files are rather strange beasts.
They’re translations of a COM file (which as you know consists simply of the
bytes that make up a program, the same bytes you will find in the left-hand
columns of a PRN listing). These translations are in another format consist
ing of all ASCII characters. For instance, if you had a “mov e,m” instruction
in your assembly listing, it would be assembled as the 8-bit (one-byte)
number 5E, which is 01011110 in binary.
In a hex file, the 5 is translated into an ASCII “5”, which is represented by
the one-byte number 35 hex, or 00110101 binary. Then, the E is translated
into an ASCII “E”, which is 45 hex, or 01000101 binary. Thus, every byte of
a COM file becomes two bytes of a HEX file.
There’s more. The entire file is broken down into groups of 16 one-byte
digits (which is 32 ASCII characters). Each such group is given a separate
load address (the place where the group is supposed to be loaded in memory)
and a count (which shows how many of the characters in the 16-character
group have actually been used). Usually they’re all used, except for the last
group in the file. In addition, there’s a checksum for each group, which
neither our program nor you need to worry about.
If we take a look at an actual HEX file, we’ll see something like this:
: 1 0C000005E2356EB7CCD15C07CCD19C07DCD15C00F0D0A
: 1 0C010007DCD19C0C907070707E60FC630FE3ADA1B000A
: 1 0C0200024C0C607CD28C0C9E55F0E02CD0500E1DAODOA
: 0 1C03000C9460D0A
: 00000000000D0A
Each of these lines is read into our BASIC program as a separate string
variable, since they are separated by carriage-return and linefeed ASCII char
acters. Let’s take a look at the first line to see how it’s constructed.
267
Soul of CP/M®
: 1 0C000005E2356EB7CCD15C07CCD19C07DCD15C00F0D0A
\ ^ Linefeed.
Always 00. '-----------Carriage return.
The 10 (hex) at the beginning of the line shows that all 16 (decimal) posi
tions in the group are filled. The load address for the group is at C000. Then
come two zeros and, finally, the 16 data bytes, in the form of 32 ASCII char
acters. This is followed by the checksum and the carriage return/linefeed
characters. In the next to the last line, only one data byte is to be loaded, at
address C030. It’s C9 (which is a “ret” instruction). Finally, in the last line,
the count is 00, which is the signal for end-of-file.
This particular HEX file happens to be for BINIHEX4, which you may
remember from the last section. You might like to compare the values in this
HEX file with those in the PRN listing shown earlier. We’ll be using this
program again in a minute as an example.
First, however, take a look at the BASIC program HEXLOAD.BAS:
This program reads in one line of the hex file at a time. Which HEX file?
The one put into the program at line 120. (You could also change it so it asks
you to type it in from the keyboard.) An IN PU T statement then reads one
string of 16 data bytes (with its load address, checksum, etc., as shown in the
preceding program.) This is assigned to the variable XX$. The program then
uses string functions to break up the line into the various components: the
load address AD$, the string of data, ST$, and each individual data byte
DA$. It doesn’t bother with the count number, preferring instead to derive
this directly from the length of the data string. Both the 4-digit address and
the 2-digit data have to be translated into decimal, since BASIC speaks only
decimal. This is done in the subroutine at line 1000. The subroutine looks, in
order, at the characters in the string H$ and then translates them into deci
mal numbers, using either two or four digits, depending on the value of IH. If
IH is 3, then four digits are used, if it’s 1, then two digits are used. The
conversion is done by adding the first (rightmost) digit to the second digit
multiplied by 16, then adding the result to the third digit multiplied by 256,
and adding this result to the fourth digit multiplied by 4096. These numbers
are derived by raising 16 to the IA power. (See the program H EX IDEC that
is described earlier in the book for an example of a similar approach.)
Once each data byte is found, it’s POKEd into the appropriate address.
When the line is finished, the program IN PU Ts a new line. When it sees a
count of 00, it quits.
The program prints the load address of each line of bytes and then prints
each byte as it’s decyphered and POKEd into memory. This way, you can be
assured that the program is doing what it’s supposed to do. And, it’s enjoya-
bly hypnotic to watch the little bytes marching across the screen and into
your computer’s memory. However, if you’re going to load really large A-L
programs, you may want to remove the statements that do the printing (lines
269
Soul of CP/M®
170 and 230) so the program will run faster. Once you’ve typed this program
into memory and stored it as a BAS file, using it is really rather simple.
Let’s use the same BASIC program that we used in the preceding examples
to test BINIHEX4. We’ll change it slightly so that the DEFUSR1 statement
sets up the entry address of the A-L routine at C000:
10 DEFINT A-Z
20 DEF USR1 =&HC000'"---------------------- -C h ange this to C000.
30 I NPUT"decimal n umbe r" ; A
40 PRINT"hex e q u i v a l e n t i s : " ;
50 D=USR1( A)
60 PRINT: PRINT
70 GOTO 30
run “HEXLOAD”
It will execute, loading in the A-L routine whose name appears in line
120 .
3. Still in BASIC, load in the BASIC program that you want to use:
run “BINTEST”
That’s all there is to it. If you try it out you should get the same results that
you got using the last approach. To simplify the loading procedure, you could
merge your own BASIC program and HEXLOAD into a single program,
which would execute HEXLOAD first, and then go on to whatever it’s sup-
270
Teamwork
posed to do. Or, you could chain them together, making the following line the
last statement of HEXLOAD to be executed:
D on’t forget to make all the load addresses the same. The number you type
in when you load BASIC must be the same as the number in the D EFU SR
statement which, in turn, must be the same as the O RG of the A-L routine.
10 DEFINT A-Z
20 DEF USR2=&HA000
30 PRINT"hex number? " ;
40 A=USR2(D)
50 PRINT"decimaL e q u i v a l e n t i s " ; A
60 STOP
271
Soul of CP/M®
272
Teamwork
The trick here is that the routine must save the contents of the HL-register
before it does anything else. T hat’s because, when we first go from BASIC to
our A-L routine, the HL-register contains a pointer to FAC-3, as described
before, and our program needs to remember where the FAC is for later use.
This is easily taken care of with a “ shld temp” instruction at the beginning of
the program, where “temp” is a two-byte location.
Having done this, we call the subroutine “hexibin” which actually gets the
value from the keyboard, and returns with it in the HL-register. Now the
problem is to get this value back into the FAC before we return to BASIC, so
that BASIC will know what value to assign to the variable A. We “xchg” the
number into the DE register, get the pointer to FAC-3 back from “tem p”,
and then move the two bytes out of DE, and store them one at a time into the
273
Soul of CP/M®
location in HL and in the following location (FAC-3 and FAC-2). This is just
where BASIC expects them to be, so our job is done and we “ret” back to
BASIC.
Something to notice here is that you can’t break into the routine from
BASIC by using control-c or the “break” key. Thus, if you changed the
BASIC program so that it continuously asked for the hex number (by adding
line “55 GOTO 30”, for example), there would be no way to get back to the
BASIC program. One way to avoid this problem is to have BASIC check for
a 0 being returned by the A-L routine. If it finds one, it knows that the user is
done with the routine and jum ps out of the loop.
There are many situations where you might want to use an A-L routine to
do something to a string variable, rather than a numeric variable, as in the
previous examples. For instance, if your BASIC doesn’t have an INSTR func
tion to search one string for another, you could add this feature with an A-L
routine. Or, you could add the LINE IN PU T or PR IN T USIN G functions.
In our example, we’ll try something a little less ambitious. We’ll write a
routine to convert any lowercase letters in a string to uppercase. This might
be useful if, for example, you wanted to search the string for a name, and
didn’t want to worry about whether the word was capitalized in the string.
BASIC handles the transferring of string variables somewhat differently
than it does numeric variables. The FAC is not used at all. Instead, the DE-
register is used to point to a three-byte “descripter” which contains the
address and the length of the string. Fig. 8-6 illustrates how it’s arranged.
So, getting to the string itself becomes a two-step process. First, we find the
address of the descripter and from that we get the address of the string. The
BASIC program for our example looks like this.
10 DEF USR1=&HA000
20 INPUT A$
30 B$=USR1(A$+" " )
40 PRINT A$
50 PRINT B$
60 STOP
274
Teamwork
String stored
in memory.
“s”
“ t”
“g”
You type in any string you like, and the program will change any lowercase
letters that it finds to uppercase. Then, it prints out both the original version,
A$, and the modified all-uppercase version, B$.
Notice the plus sign and the space following the A$ in line 30. W hat’s all
that about? It’s a subtlety that has to do with the way BASIC handles string
variables. If we had written:
30 B$=USR1( A$)
275
Soul of CP/M®
; g e t c h a r a c t e r c o u n t and a d d re s s o f s t r i n g
;from d e s c rip te r
AOOO EB xchg ;p u t d e s c r ip te r p t r in hi
A001 4E mov c,m ; p u t count in c
A002 23 i nx h ;move p o i n t e r t o a dd re ss
A003 5E mov e,m ; l o - b y t e o f a d d r e ss i n e
A004 23 i nx h
A005 56 mov d,m ;h i- b y te in d
A006 EB xchg ;de (address o f s t r i n g ) in
f
; cha nge l o we rc as e l e t t e r s i n s t r i n g t o upperc.
A007 7E newch mov a,m ; g e t c h a r a c t e r f ro m s t r i n g
A008 FE61 cp i ’a’ ; i s i t l e s s t han " a " ?
AOOA FA15A0 jm no low ; y e s , so n o t l o we rc as e
AOOD FE7A c pi V ; i s i t more t han " z " ?
AOOF F215A0 jp no low ; y e s , so n o t l o we rc as e
A012 D620 sui 20h ; c o n v e r t t o uppe rc as e
A014 77 mov m,a ; p u t back i n s t r i n g
A015 23 no low i nx h ; i n c r e m e n t add re ss
A016 OD dcr c ; done y e t ?
A017 C207A0 jnz newch ; n o, g e t n e x t c h a r a c t e r
/
; b a c k t o BASIC
A01A C9 ret
F
A01B end
address of the string (which goes back in the DE-register). Once we’ve got
this address, we “xchg” it into HL, and we’re ready to start changing lower
case letters to uppercase letters.
We don’t have to pass anything back to BASIC when we return, since
BASIC already knows where the string is that we modified.
276
Teamwork
BACK TO BASICS
Now that you know how to add assembly language routines to BASIC, you
may not have so much free time anymore. All of the BASIC programmers,
who can’t get the computer to do what they want in that language, will come
to you asking, “Please, just a little routine to do a fast bubble-sort of my
data?”, “ Please, just a little routine put my output in columns on the
screen?”, and so on. You may have to get an unlisted number.
277
■
CHAPTER 9
characters that should activate these features are already being used
by your word-processing program for something else. W hat you
need is for your printer to respond to a different set of control char
acters, but you don’t want to rewire the printer.
Given any of the above situations, and assuming that you don’t have a
resident programmer on your staff, what should you do? Stay tuned; you’ll
find the answers in this chapter.
While our discussion is mostly about printers, the techniques used are
applicable to other input/output devices as well, such as modems, tape trans
ports, different video terminals, or whatever.
As you may recall, the external part of C P /M is the CCP (Console Com
mand Processor), which looks at what you type on the keyboard and takes
action accordingly. The CCP is the outward facade that C P /M presents to
the world. To perform I/O , the CCP then calls the BDOS, or Basic Disk
Operating System. Applications programs do the same thing, using a “call
bdos” instruction, as you know from writing your own programs as described
in the previous chapters. The BDOS is a section of code that is the same for
all versions of C P/M , no m atter what machine it is running on. The main
purpose of BDOS is to handle disk files, but it also channels I/O requests for
other peripherals.
In order for BDOS to actually carry out its duties, it must at some point
communicate with the actual physical devices connected to the machine: the
disk drives, video screen, keyboard, printer, and so forth. Since these per
ipherals can come from a variety of manufacturers and can operate in a vari
ety of different ways, the subroutines which drive them must be different for
each piece of equipment. This collection of subroutines is what constitutes
the BIOS. These subroutines are called “driver” routines, meaning that they
“drive” a particular peripheral device. When you change from one kind of
printer to another, it’s this driver in the BIOS that must be modified to
“ speak” to the new printer.
z
THE USER
/ 7!
CCP
z 71
BDOS
z BIOS
71
Driver
Direct connection
routines
to I/O devices.
1/
1/
1/
1/
Fig. 9-1. BIOS communicates with I/O devices.
281
Soul of CP/M®
Notice that although the BIOS is at the innermost position in C P/M , it has
(as do many innermost souls) a direct connection with the outside world. In
fact, it is the only part of C P /M that can actually communicate with physical
devices.
W hat we’re going to do in this chapter is teach you how to modify a driver
routine so that your C P /M system can operate with a different printer. The
techniques involved will also work for adding or changing other peripherals
(such as your video display or the addition of a modem). Changes to the disk
drives are considerably more complicated and should probably be left to the
dealer unless you are a very ambitious programmer.
Because every system uses different hardware, every BIOS will be different.
We can’t, therefore, tell you exactly how to modify your own BIOS. W hat we
can do, however, is show you an example: how to modify a particular BIOS.
Once you understand our example, you should be able to apply the same
techniques to your own situation.
There are three steps involved in writing a new driver for your BIOS, and
we’ve given each of them a separate section in this chapter. The three steps are:
1. Learning your way around the BIOS. You need to be able to find the
existing driver so that you can see what it looks like, how it relates to
the rest of the BIOS, and where to put the new driver.
2. Writing the new driver routine and testing it.
3. Inserting the new driver into the BIOS file and writing the new BIOS
to the system tracks of your disk.
These steps are all somewhat involved but we’ll cover them slow and easy,
and when we’re done and you look back on everything, it’ll seem easy!
There are two things you need before you can modify your BIOS. The first
is the ASM file of the BIOS. This is supplied (or can be) by the friendly
people who put your system together for you. It contains the actual 8080
code that communicates with I/O devices. This file is usually called some
thing with “ BIOS” in it, like IOBIOS.ASM or GBBIOS.ASM. If the file con
tains the I/O for the disk system, then it will be very long, like 20 or 30 pages
or so when printed out. But, often, the supplier will leave out the part of the
282
The Innermost Soul of CP/M
BIOS that deals with the disk system and then it will be fairly short, on the
order of a half-a-dozen pages. You need this file because it’s what you’re
going to modify. That is, you’re actually going to use your word-processor
program to alter parts of a section of this file. Then, you’ll reassemble it with
ASM and write it back onto the “system tracks” of your disk.
The second thing that you’ll need is the specifications for your U A RT or
serial board. “U ART” stands for “Universal Asynchronous Receiver/Trans
mitter.” The U ART is a “chip” or integrated circuit that is installed on one
of the boards in your computer, namely the “ serial board,” which allows the
computer to communicate in serial mode with external devices. We’ll talk
more about what all this means in the next section. U ntil then, ju st get your
hands on the spec sheet. It will tell you what numbers to output to what
“port” in the U ART in order to send signals to various I/O devices.
Some versions of the BIOS.ASM file include information on the UART at
the beginning of the listing. Thus it is possible to modify your BIOS without
the actual spec sheet, provided that the U ART is already installed and oper
ating and your listing has this information. It’s also easier if you have a
device already operating that is similar to the one you plan to install. That is,
if you already have a printer running and want to change to another printer,
you may not need the spec sheet or the comments in the listing. This is
because you can use the existing driver as a model for what to do. But, if you
don’t have any printer at all, then you really need the spec sheet. In any case,
it will make your life a little easier.
BI OS V E R S I O N 2 . 7 : S I N GL E D EN S IT Y , 128 B/S
DO UB L E D E NS I TY , 256 B/S
DO UB L E D E NS I TY , 1024 B/S
***************************************************
284
The Innermost Soul of CP/M
This BIOS starts out describing itself, where it comes from, and what it’s
for. It comes from “WW Components,” is for the Imsai 8080, and is version
2.7 of the BIOS.
Next, we come to a very im portant part of the BIOS. All BIOS listings
contain some calculations in the beginning that define and then set up the
beginning address of the BIOS, itself, in memory. The addresses are usually
calculated according to a set of rules laid down by Digital Research (the
developers of C P/M ). They start out by specifying the memory size that the
BIOS is working with. In the present example, we’re looking at a BIOS that’s
set up for a 56K memory size. (If your BIOS has other weird statements in it
at this point, such as various options that must be set to true or false, ignore
them. They won’t influence this discussion.)
In the first line, we see that the size of the com puter’s memory, MSIZE, is
set to 56 (38 hex). All BIOS.ASM files should have such an equate. If we
want to change our BIOS to work on a machine with a different memory size,
the first thing that we will do is change this number to the appropriate value.
The next im portant number looks a bit strange; it’s called BIAS. (In other
listings, it may have other names, like IOBIAS.) It’s the distance, in memory,
between where the CPM system is on your system and where it is on a “vir
gin” C P /M as it comes from the factory. Brand new systems are configured
for 20K memories, and are then moved up to the top of memory where they
will reside in a particular machine. BIAS is related to a certain magic
num ber—yes, a genuinely magic number called N —which we will be discuss
ing in the last section of this chapter: how to insert your driver into the BIOS.
We’ll have a lot more to say about this later.
BIAS is used to define the starting locations of the major parts of the
C P /M system: CCP, BDOS, and BIOS. The “standard” starting location for
the bottom of the CCP is 3400 in a 20K system. As you can see, the CCP is
806 hex bytes long in this listing, since BDOS starts at CCP + 806H. The
CCP and the BDOS, together, are 1600 hex bytes long so, in our 56K system
at least, BIOS ends up starting at D200 hex.
In the next section of code, shown in Listing 9-1, the BIOS very kindly tells
us something about the UART: namely, the meaning of each bit when we
read the “status” of an I/O device. Thus, bit 1 is “receiver ready,” and so
285
Soul of CP/M®
I / O DEFINITION EQUATES
★★★★I***********************************************
USEFUL THINGS TO KNOW
MPU-B STATUS PORT
(TERMINAL/TRANSMITTER REFERENCE)
0003 =
CRTST EQU 3H ; CRT STATUS PORT
0002 =
CRTDATA EQU 2H ; CRT DATA PORT
0015 =
IKBST EQU 15H ; IKB1 STATUS PORT
0014 =
IKBDATA EQU 14H ; IKB1 DATA PORT
F803 =
VIODATA EQU 0F803H ; VIO DATA PORT
0002 =
RXRDY EQU 02H ; CONSOLE STATUS
/ READY BIT (RxRDY)
0003 = IOBYTE EQU 0003H ; INTEL I / O BYTE
F800 = V I OI NI T EQU 0F800H ; V I 0 I N I T ENTRY POINT
FFFD = VIOID EQU OFFFDH ; POINTER TO VIO ID
0023 = PRTS EQU 23H ;PRINTER STATUS PORT
0022 = PRT EQU 22H ;PRINTER DATA PORT
286
The Innermost Soul of CP/M
Notice especially PRTS and PRT, the printer status port and the printer
data port, respectively. We’ll be using the numbers that they’re equated to, 23
hex and 22 hex, when we write our printer driver.
In the next section of code, we finally get to the actual beginning of our
program: the O RG statement. Usually, this location is defined in terms of
various other locations which, in turn, are defined in terms of the memory
size of your computer, among other things. In this case, the O RG is at the
start of BIOS plus 0C70 hex. The listing doesn’t start at the beginning of
BIOS because all the drivers for the disk system are located there, between
D200 and D200 + 0C70. Our listing is really only a small part of the BIOS:
the nondisk part.
But think about this. How is BDOS, which wants to call some driver rou
tine in the BIOS, going to find it? If we fool around- as we’re going to do in
this chapter—adding instructions to the BIOS and reassembling it so we can
insert our driver in it, the starting addresses of all the drivers following the
one we changed will be different. How will BDOS find them? The answer is
that it makes use of a clever programming idea called a “jum p table.”
A jum p table is merely a bunch of jum ps at the beginning of the listing.
Each jum p goes to one of the driver routines, like LIST, and CONST, and so
on. Since these jum ps are part of the listing we’re going to reassemble, the
values in their address fields will change when we reassemble the file. Thus,
287
Soul of CP/M®
BIOS doesn’t care where the driver routines themselves are, it just cares
where the jum p table is, and the jum p table is always in the same place: at
the beginning of the listing. BIOS “calls” one of the locations in the jum p
table, and the jum p takes BDOS to the proper driver.
Notice, however, that BDOS assumes the jum ps in the jum p table (Listing
9-2) will always be in the same order. When it calls on the second jum p in the
table, it expects to find a jum p to LISTIO, not PUNCHIO. That’s why the
listing says “ DO NOT REA RRA N G E JU M P TABLE.”
Now we can actually start to follow a trail through the listing to our desti
nation. Look at the jum p table; the second jum p is to LISTIO. This is to the
LIST device. As you recall, C P /M permits different physical devices to be
assigned to different logical devices. There are four logical devices:
CON:
RDR:
PUN:
LST:
which stand for console, reader, punch, and list. There are also various
physical devices, like TTY:, and CRT:, and LPT:. You can change the
assignments of physical to logical devices by using STAT. W hen you do
this, STAT changes something called the IOBYTE, which is location 3 in
memory.
Now, when BIOS gets a call to the LIST routine, it doesn’t know what
physical device it’s intended for until it checks the IOBYTE. Based on
w hat it finds there, it will either go to the driver for the printer, or to
some other driver. T h at’s why LISTIO and LISTSTIO (for “ list status
I /O ” ) aren’t really driver routines but, instead, are a different form of
jum p table. H ere’s w hat they look like
288
The Innermost Soul of CP/M
; LIST CHARACTER IN C
Well, would you look at that! It’s the actual instructions to tell the UART
to accept a character and send it on to the printer. We’ve reached the end of
our journey; this is the very routine that we’re going to modify. For a change,
it doesn’t call another routine—it’s the end of the line.
The PRTS (printer status) and PRT (printer) constants were defined ear
lier to be 23 hex and 22 hex. These are the “ports” that are accessed by the
IN and OUT instructions.
The IN Instruction
IN and OUT instructions are the chief way that the 8080 microprocessor
communicates with the outside world. As their names imply, IN gets data
from a data port and puts it in the A-register, while O UT sends data from the
A-register to a data port. There are 256 possible data ports: the appropriate
289
Soul of CP/M®
Before IN 22 Is Executed:
/ 21
A-register
/
22
41
/
23
After IN 22 Is Executed:
/ 21
A-register
/
z 41
22
K 'J
23
one is specified by a 2-digit hex number (or a label equated to it) in the
address field.
Note that the data ports and their addresses are not the same as memory
and its addresses. The data ports are a separate group of registers and, since
there are only 256 of them, they are addressed with 2-digit hex numbers. This
is illustrated in Fig. 9-2. Thus, the 5A in the instruction “ IN 5A” refers to a
data port, not to memory address 5A.
The byte that is read into the A-register can either be data, such as a 41 hex
“A” character read from the keyboard, or it can be a “ status byte,” whose
purpose is to inform your program about the status of a U ART or an input/
output device. The values of a status byte must be known from the operation
manuals for the U ART or input/output device.
As noted above, O UT takes an 8-bit byte from the A-register and puts it
into the data port specified in the operand field of the instruction. This byte
can either be data that is to be transferred to an output device (such as a 41
290
The Innermost Soul of CP/M
Before OUT B1 Is Executed:
A-register BO
B1
7F
B2
A-register BO
7F B1
7F
B2
OF = 0 0 0 0
66 =
1 11 1
0 110 0 110
kI Memory
address.
0 1 1 0 1 0 0 1 = 69 ■
69
291
Soul of CP/M®
Examples:
IN 5Ah
OUT LABEL
This is a useful instruction for testing bit patterns, because it will immedi
ately tell you if all the bits in a certain bit configuration are set to 1. Simply
XRI the bit pattern that you want to see with the bit pattern in the A-regis
ter. If all the bits you specify are set, but nothing else is, the result will be 0.
For example, if the A-register contains 07 hex and you XRI it with 85 hex,
you’ll have:
A-register 0000 0111
Constant 1000 0101
Result 1000 0010
which is not zero. However, if the A-register contains 85 hex, you’ll get:
292
The Innermost Soul of CP/M
The first thing we do in LPTOUT is read the status of the printer port to
see if the printer is ready to receive data. We do this with an IN instruction to
the port that holds the printer status: number 23 hex. This will return a byte
that looks like this:
7 6 5 4 3 2 1 0
Now, what we want to see when the printer is ready to receive a character
is this: the DSR, TxE, and TxRDY bits must be set. The other bits can be
anything they like. So we first mask off the other bits with an ANI (A N D
immediate) instruction, using an 85 hex, which is a 10000101 binary. As a
result, only bits 7, 2, and 0 can have a value other than zero. They may be
zero, but they may also be a one. All the other bits are definitely a zero.
Now, we want to make sure that bits 7, 2, and 0 are set to zero, so we XRI
the A-register with 85 again. If not, we go back to check again with the JN Z
instruction.
Once all the bits are set properly in the status word, we’re ready to receive
data. The data byte has been in the C-register all along. (Remember how you
put it there yourself before doing a call to BDOS?) So we move it to the A-
register, and then OUT it to the printer output port, which is 22 hex. Then we
return.
W hat could be simpler?
I /O Initialization
INITVC:
I N I T UART (RECOMMENDED CMDS BY IMSAI)
DEF8 AF XRA A
DEF9 D303 OUT CRTST
DEFB D303 OUT CRTST
DEFD D303 OUT CRTST
DEFF D323 OUT PRTS
DF01 D323 OUT PRTS
DF03 D323 OUT PRTS
DF05 3E40 MVI A,40H ; RST 8251
DF07 D303 OUT CRTST
DF09 D323 OUT PRTS
DFOB 3EAE MVI A,0AEH ;MODE
DFOD D303 OUT CRTST
DFOF D323 OUT PRTS
DF11 3E27 MVI A,27H ;CMD
DF13 D303 OUT CRTST
DF15 D323 OUT PRTS
If you’re changing from one printer to another, you probably won’t have to
add anything to this initialization process, but if you add a driver that wasn’t
there before, then you’ll have to add code in this section to initialize the new
ports on your UART.
294
The Innermost Soul of CP/M
*******************************************************
I / O DEFINITION EQUATES
*******************************************************
USEFUL THINGS TO KNOW
MPU-B STATUS PORT
(TERMINAL/TRANSMITTER REFERENCE)
0 = TxRDY T R A N S M I T T E R READY
1 = R xR DY R E C E I V E R R EA DY
2 = TxE T R A N S M I T T E R E MPTY
3 = PE P A R I T Y ER ROR
4 = OE O V E R R U N ER ROR
5 = FE FR A MI N G ER ROR
6 = S YN D E T S YCN D ET E C T
295
Soul of CP/M®
*******************************************************
USER CUSTOMIZED I / O DEVICES. *
DO NOT REARRANGE JMP TABLE. *
*******************************************************
296
The Innermost Soul of CP/M
297
Soul of CP/M®
; READER IN - RDR:
READERIO:
DED7 CDE3DE CALL DISPATCH
DEDA 07 DB 7 ;USE IOBYTE BITS 3-2
DEDB 4ADF D W CRTIN ;00 - TTY: (CRT INPUT)
DEDD 74DF DW RDRIN ;01 - PTR: ( H . S . READER INPUT)
DEDF 4ADF DW CRTIN ; 10 - UR1 : (CRT INPUT)
DEE1 4ADF DW CRTIN ; 11 - UR2: (CRT INPUT)
DISPATCH:
DEE3 E3 XTHL ;SAVE CALLER'S H, GET TABLE ADD
DEE4 56 M OV D,M ;SHIFT COUNT
DEE5 23 INX H ; POINT TABLE
DEE6 3A0300 LDA IOBYTE ;GET 10 ASSIGNMENTS BYTE
DEE9 07 DSHFT: RLC
DEEA 15 DCR D
DEEB C2E9DE JNZ DSHFT ; SHI FT TO POSITION BITS
DEEE E606 ANI 06H ;MASK BITS
DEFO 5F MOV E, A ; D ALREADY CLEAR
DEF1 19 DAD D ; INDEX INTO TABLE
DEF2 7E MOV A,M
DEF3 23 INX H ;TABLE WORD TO HL
DEF4 66 MOV H,M
DEF5 6F MOV L,A
DEF6 E3 XTHL ;PUT ADDR OF ROUTINE, GET CALLER'S H
DEF7 C9 RET ;G0 TO ROUTINE
• 'k'k'k'k'k'k'k'k'k'kick'k'k'k'k'k'k'k'k'k'k-k'k'k'k'k'k'k'k'k'k'kic'k'k'k'k'k'k'k'k'k'k'kit'k'k'kif'k'k'k'k'k
298
The Innermost Soul of CP/M
■*******************************************************
; LIST CHARACTER IN C
■ *******************************************************
DF35 DB23 LPTOUT: IN PRTS ; CHECK STATUS PORT
DF37 E685 ANI 85H ;CHECK BOTH READY BITS
DF39 EE85 XRI 85H
DF3B C235DF JNZ LPTOUT
DF3E 79 MOV A,C
DF3F D322 OUT PRT
DF41 C9 RET ;USER PRINTER 10
299
Soul of CP/M®
• *******************************************************
; RETURN LIST STATUS (FF I F READY, ELSE 0)
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
DF42 3EFF LISTSTC:MVI A,0F FH
DF44 C9 RET
• *******************************************************
; CONSOLE STATUS RETURNED IN A.
* *******************************************************
CRTSTAT:
DF45 DB03 IN CRTST ;STATUS PORT
DF47 E602 ANI RXRDY ;TEST RxRDY
DF49 C9 RET ; Z = 1 , CHAR NOT READY
:
• k'k'k-k'k'k'k'k-k-kie-k'k'k'k'k'k'k'k'kic'k'k'kicic'k'k'kicic'kic'k'k'kie'k'kic'k-k'k'k'kic'kieic'k'k'k'k-k'k
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
"k'kickic'k'k'k'k'k'k'k'kic'k'k'k'kic'k'kick'k'k'k'k'k'k'k'k'k'k'k'kie'k'kic'k'k'k'k'kie'k'k'k'k'k'k'kic'k'k
300
The Innermost Soul of CP/M
• *******************************************************
; IKB1 IN RETURNS THE CHARACTER IN A
■ *******************************************************
IKBIN:
DF64 CD5FDF CALL IKBSTAT
DF67 CA64DF JZ IKBIN ;GET RxRDY
DF6ADB14 IN IKBDATA ;GET CHARACTER
DF6C E67F ANI 7FH ;STRIP PARITY
DF6E C9 RET
• •k'k'k'k'k'k'k'k'k'k'k'k-k'k'k'k'k'k'k'k'k'k-k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k
■ *******************************************************
; PUNCH CHARACTER IN REGISTER C
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
PUNOUT:
DF73 C9 RET ;NULL ROUTINE
• k'k'kie'k'k'k'k'k'k'k'k'k'k'kit'k'k'k'k'k'k'k'k'kic'k'k'k'k'k'k'k'k'k'k-k'kic'kickick'k'k'k'k'k'k'kic'k'k'k
RDRIN:
DF74 3E1A MVI A, 1AH ;ENTER END OF FILE
; (REPLACE LATER)
DF76 E67F ANI 7FH ;REMEMBER TO
; STRIP PARITY BIT
DF78 C9 RET
DF79 END
In order to create a new driver for our BIOS, you first must determine the
kind of driver program you need to write. You must know what kind of
printer you have and which communications “protocol” it uses. “ Protocol” is
just a fancy word for “procedure.” In this case, it means the procedure by
which the the printer tells the computer that it is ready to accept data.
301
Soul of CP/M®
With some printers, you can simply wait for it to be ready, throw a charac
ter at it, wait for it to be ready, throw a character at it, and so on, until you’re
done, and it will print the characters ju st fine. Such a printer is called a “stan
dard serial printer,” or a “teletype-like printer.” The Epson is such a beast.
Other more complex printers, such as the “daisy-wheel” printers, require one
of two possible advanced protocols, either “xon/xoff” or “etx/ack.” For
instance, the NEC printer needs etx/ack while the Diablo 630 likes xon/xoff.
These protocols are designed to allow the printer to work with a bunch of
characters at a time rather than with single characters.
X O N /X O FF Protocol
You have probably already used xon/xoff and not known it. X on/xoff is
the same thing as the “control-s/control-q” that C P /M uses to freeze and
unfreeze the scrolling of the display on the screen. In C P /M , control-s will
freeze the display. Striking any key, thereafter, generates a control-q which
will unfreeze the display. Control-s means xoff, i.e., “turn off transmission”
while control-q means xon, i.e., “turn on transmission.”
A printer uses this technique of xon/xoff when it has a built-in “character
buffer” that can hold a certain number of characters for printing. The buffer
may be, for example, 1024 bytes long. W hat the buffer does is allow the com
puter to send characters to the printer at a fairly fast rate (say, 1200 baud, or
about 120 characters per second) and then hold them while the slower printer
mechanism prints them out (which may occur at roughly 30 characters per
second). This way, while the buffer in the printer is emptying, the computer
can go on its merry way and do more processing. Provided the com puter is
set up properly, you could then be editing a file while the printer was spitting
characters out on the paper, and your text editor would not be slowed down
by the 30 character/second printer. You would only notice a delay when the
printer needed its buffer filled again, and this would occur at a rate of 1200
or more baud, so it wouldn’t take long.
Normally, the way all this happens is that after your printer driver sends
each character, it reads a status word from the printer and looks to see if the
printer is sending back a control-s, which means “Stop! my buffer is full.
D on’t send any more characters until it’s empty.” If there is no control-s
being sent back, the computer sends the next character, and so on, until it
302
The Innermost Soul of CP/M
receives a control-s. At that point, it goes into a wait loop searching for a
control-q to come from the printer. The control-q means the printer buffer is
once again empty. (“Hello, I’m empty; you can send more data.”) Fig. 9-5
shows how this looks in flowchart form.
The point of all this protocol is that the computer only waits for the printer
after every 1024 characters, instead of after every character, and is, therefore,
free to do other tasks while printing is taking place.
ETX/ACK Protocol
In the last section, where we explored the BIOS listing, we found a driver
for the simplest kind of printer—a “teletype-like” device with no advanced
protocol. W hat we’re going to do in this section is write a driver for a printer
that uses xon/xoff protocol. Then, in the next section, we’ll show you how to
insert this driver into your BIOS so your entire C P /M system can use the
new printer.
Specifically, our printer will be a Diablo 630 daisy wheel. We are using it
for word processing and it is hooked to our C P /M system through a serial
board running at 1200 baud. The serial board uses an Intel 8251 UART,
which is what our driver must talk to.
Look back at the driver in the last section. When it wants to know if it can
send a character, it checks to see if 3 bits are set: number 7 for “D ata Set
Ready,” number 2 for “Transmitter Empty,” and number 0 for “Transmitter
Ready.” Here’s how the status word looks with these bits set:
304
The Innermost Soul of CP/M
1. We put the driver routine at 100 hex, just like any other program. We
can use either the D D T “ a” command, or assemble it with ASM.
2. We put a “jm p 100” instruction at the start of the printer driver rou
tine in the BIOS.
305
Soul of CP/M®
; New x o n / x o f f p r i n t e r d r i v e r .
; (was j u s t a RET r e t u r n i n s t r u c t i o n . )
We’ll assume at this point that you’ve written an appropriate driver routine
of your own. It may not be perfect, but it’s ready for a preliminary test. Thus,
as we describe how to test our routine, you can—if you’re ready—follow the
same steps with your own routine.
First, you have to know where to put the “jum p 100” instruction. This isn’t
hard if you’ve assembled your existing BIOS into a PRN file. Simply look
through the listing for the printer driver routine and write down the address
where it starts. In our 56K system, this address is DF35 hex, as you can see
from the BIOS listing that was described in the last section.
306
The Innermost Soul of CP/M
So, either write your driver with your word-processing program (call it
NEWDRIVE.ASM), assemble it to get a HEX file, and call it in with DDT:
A>ddt n e w d r i v e . h e x
or, type it in directly from D D T using the “a” command. In either case, ORG
it at 100 hex.
At this point, your printer should not be operating (you have not toggled it
on with control-p). Use the “a” command to insert a “jm p 100” at the start
of the driver routine in BIOS. For our particular BIOS, we’d say:
-aDF35
DF35 jmp 100
DF38 .
N ote that you are modifying the very BIOS that you’re operating with. If you
do something wrong, the system may die, so save your files before you get in
too deep.
Now comes the moment of truth. Toggle on the printer by typing control-
p. H it “return” a few times. The printer should respond. Type something,
and then hit return. It should be printed out. If the wrong thing, or nothing,
happens, either your driver is defective or your “jm p 100” instruction is in
the wrong place. Back to the old drawing board.
did not put the disk part of BIOS in your BIOS.ASM file, then they put it in
the MOVCPM program. W hat MOVCPM may not have in it are the drivers
for the other I/O peripherals: the console, the printer, and so on. They’re
probably in the BIOS.ASM file. MOVCPM may have these routines in it, but
they may not work on your equipment, or they may be merely skeletal drivers
that don’t work on any equipment.
Here’s how MOVCPM works. When you call it, it loads three things into
memory: (1) the MOVCPM program itself, (2) the CCP, BDOS, and maybe
part of BIOS in the form of a COM file, and (3) a bit map, which tells it
which bytes of the COM file need to be “relocated” and which don’t.
One question that should immediately occur to you is, “where in memory
does the MOVCPM put this COM file?” It can’t put it in high memory where
C P /M normally goes, since the actual system we’re operating with is already
there, and you can’t make major changes in the very same sections of code
that constitute your operating system, without getting into big trouble. So
MOVCPM keeps this version of C P /M in low memory, starting at location
900 hex, which just leaves room for the program and the bit map below it.
This version of C P /M is called the system “image,” a name that means that
the instructions of code look just like the actual operating version of C P /M
in high memory, but are in the wrong place to actually execute.
Fig. 9-6 illustrates what memory looks like when MOVCPM is at work.
When you call MOVCPM from C P /M , you need to specify two parameters.
The first is the size memory that your machine has, 64K or whatever. Second,
you need to tell it whether you want the image of C P /M to be moved to high
memory and executed. This is something you almost never want, since the
C P /M image is probably incomplete and won’t work, and will result in a
crashed system if you try to execute it. If you don’t want this to happen, you
type an asterisk after the size:
A>movcpm 56 *
This means “leave the image in low memory; don’t relocate or execute it.'
This means “we want to generate CP/M for a 56K system.”
You can also use an asterisk in the memory field if you want to simply use
all the available memory.
308
■
/ / Top of memory.
BIOS
/
“Active” CP/M system.
BDOS
FBASE /
CCP
CBASE /
Space available, depending
on size of system.
/
BIOS (maybe)
/ “ Image" of CP/M.
BDOS
/
CCP
900 /
Bit Map
/
MOVCPM
100 /
Zero Page
0 /
309
Soul of CP/M®
A>movcpm 56 *
Now that you know how to use MOVCPM, you can be admitted to the
small and privileged group who know how to find the magic num ber N.
W hat is this number? Really, it is the difference, or offset, between where
MOVCPM puts the image of C P /M and where C P /M actually goes in high
memory.
In Figure 9-7, notice how the “image” of CPM that is placed in low mem
ory by the MOVCPM program is related to the image of C P /M that will be
placed on the system tracks of the new disk. This new image will occupy
exactly the same addresses as the C P /M that is currently running in high
memory, except for the changes made to BIOS by the addition of the new
driver.
The CCP is located at 980 hex in the system image, but will be at BCOO hex
(in our 56K system) when actually installed and running. BDOS is located at
1186 in the image, but will be at C406 when running, and so forth. You can
see that all these pairs of numbers are related by the same constant, which we
can find by subtracting any two of them.
310
The Innermost Soul of CP/M
In order to find the magic number N, we need to know at least one pair of
addresses: the one in the MOVCPM image in low memory and the one that is
in the running C P /M in high memory. A good “pair” to work on is CBASE,
the bottom of the CCP. The first half of this is easy because, in the system
image, the CCP is always at 980 hex, no matter what size system you have.
CBASE for high memory, as we discussed before in the chapter on BASIC, is
not quite so easy to find. However, if you look at locations 6 and 7 hex in
memory while C P /M is running (not DDT), you’ll find an address that is
close to the start of BDOS. The CCP is usually about 800 hex bytes long, so if
you subtract 800 from this address, you’ll get CBASE. In our case, C406 -
800 = BC06. But we know that the CCP is going to start on a page bound
ary, so we figure that it’s probably at BC00.
Another way to find CBASE is to look at the BIOS.PRN (not the ASM)
listing. As we described earlier in the chapter, CBASE and several other fas-
DFFF
_____ /
Old BIOS New BIOS D200
/
BOOT BB80
(Unused)
/
2D7F Image written
New BIOS to system tracks.
1F80
BDOS
1186
CCP
980
BOOT
900
Bit Map
311
Soul of CP/M®
cinating addresses are often part of the address location arithmetic in the
beginning of the file. Checking this file, we find that BCOO is, in fact, the start
of the CCP.
D D T ’s H ex Arithmetic Function
Did you know that D D T had a handy function that will perform arithme
tic on hexadecimal numbers? Of course you did. If we type:
-hx,y
where x and y are 4-digit hex numbers, D D T will print out x + y and x-y in
hex, for our gratification and amusement.
In this case, we want to know the difference between, say, 980 and BCOO.
It would also be nice if we got a negative number, so that we could simply
add it to other numbers later to perform the conversions. Thus, we get into
D D T and type:
-h980,bc00
D D T responds with:
C580,4D80
The difference between these two numbers is what we want: 4D80. It is, in
fact, our magic number N.
312
The Innermost Soul of CP/M
N ote that for our 56K system, it gives a value of 4580 instead of the 4D80
that we know is correct. The difference between these two values is 800 hex,
which is just how much additional space our modified BIOS takes up.
If you have the standard-size BIOS, then the table will be useful to you,
but you should verify the actual location of your C P /M system by one of the
methods previously described before you accept the figures in the table at
face value.
We need to know this number for two reasons. First, we want to be able to
look at certain sections of code in the MOVCPM image in low memory and
know what we’re looking at. We know where things are in BIOS in high
memory from the BIOS.ASM listing, and we need to be able to translate this
into equivalent locations in low memory. The magic number N does this, as
we shall see.
Secondly, we’re going to use another one of D D T ’s useful features-the
ability to load a HEX file with a “bias” or offset, instead of at the ORG
address of the file. Thus, if we have a HEX file that is ORGed at, say, D200,
and we want to load it into memory somewhere else, say at 1F80, we simply
figure out the magic number that is the difference of these two numbers and
type it into D D T following the “r” (for “read”) command. As you can see,
this is the same old magic number, 4D80. If, for example, we want to load a
HEX file called NEWBIOS.HEX (which is ORGed at D200) using this bias,
we type:
-inewbios.hex
-r4d80
At this point, you should have a working driver routine that you’re ready
to insert into your C P /M operating system. In the procedure that we’re going
to describe, we’ll make use of the knowledge you’ve acquired in the previous
sections.
The first thing that we want to do is create a new version of the BIOS.ASM
file: one which incorporates your new driver. (Remember that this file will
have different names depending on your system: BIOSIO.ASM,
GBBIOS.ASM, or whatnot.)
Start by making a copy of the old BIOS.ASM file. Call it
NEWBIOS.ASM:
A>pi p n e w b i o s . a s m = b i o s i o . a s m
A>asm newbios
You can now use MOVCPM to create the C P /M image, as described in the
last section. If you already have the system image (CPM56.COM or what
ever) stored as a file, you can skip this step. Refer to the last section to see
how MOVCPM is used.
314
The Innermost Soul of CP/M
A>movcpm 56 *
A>save 45 cpm56.com
We’re going to use D D T to do the actual merging of the new driver into
the system image. Load the system image as you call DDT:
A>ddt cpm56.com
To make sure that everything is where you think it is, use D D T to explore
this image. Here’s the beginning of the CCP in the system image:
-d980
0980 C3 5C BF C3 58 BF 7F 00 20 20 20 20 20 20 20 20 .\. .X ...
0990 20 20 20 20 20 20 20 20 43 4F 50 59 52 49 47 48 C0PYRIGH
09A0 54 20 28 43 29 20 31 39 37 39 2C 20 44 49 47 49 T (C) 1979, DIGI
(e tc.)
It should be just the same as the running CCP in high memory, so look at
that:
-dbcOO
BCOO C3 5C BF C3 58 BF 7F 00 20 20 20 20 20 20 20 20 . \ . .X . ..
BC10 20 20 20 20 20 20 20 20 43 4F 50 59 52 49 47 48 C0PYRIGH
BC20 54 20 28 43 29 20 31 39 37 39 2C 20 44 49 47 49 T (C) 1979, DIGI
(e tc.)
Using the “d” command lets us see that the sign-on message is really there.
Check the beginning of BIOS. It should be a jum p table:
- 1 1 f 80
1F80 JMP D3D3
1F83 JMP D334
1F86 JMP DE79
1F89 JMP DE7C
(etc . )
315
Soul of CP/M®
- Ld200
D200 JMP D3D3
D203 JMP D334
D206 JMP DE79
D209 JMP DE7C
(etc . )
Find the old driver routine (if there is one) in the system image and in the
running BIOS. They may not be the same.
We’re going to use D D T to merge the NEWBIOS.HEX file into the image
of C P /M in low memory. This is where the magic offset number N comes in.
Although NEWBIOS.HEX is ORGed in high memory (at DE70 in our case),
we want to lay it down on top of the BIOS part of the system image in low
memory (at 2BF0 in our case).
-inewbios.hex
-r4d80
That reads in the file with an offset of 4d80. You should now look at the
image with the “1” command to make sure that the new driver is where you
want it to be. If so, you’re ready to save the image as a file back onto the disk.
-gO
A>save 45 cpm56n.com
316
The Innermost Soul of CP/M
Now, we’re ready to actually create a new system disk. Take a formatted
disk and put it in drive B.
SYSGEN is often used, as you no doubt know, to simply copy the current
C P /M system from the system tracks of the working disk to those of a
formatted disk. In that case, no file name is specified when we call it. How
ever, we want to use the CPM56n.COM file when we generate our new sys
tem, so we type:
A>sysgen cpm56n.com
SYSGEN VER 2 . 0
DESTINATION DRIVE NAME (OR RETURN TO REBOOT)b
DESTINATION ON B, THEN TYPE RETURN-------------------
FUNCTION COMPLETE
DESTINATION DRIVE NAME (OR RETURN TO REBOOT)-
SYSGEN will write the new version of the operating system onto the system
tracks of the new disk.
T hat’s it! You’re done! You have a new custom-configured operating sys
tem that you made yourself. There are a lot of people out there who will pay
good money for this sort of talent. Test it out using D IR and TYPE to see if
it will really print out when you toggle on the printer with control-p. If you’re
testing an xon/xoff or an etx/ack printer, you’ll need to send enough charac
ters at once to fill up the printer buffer.
If it doesn’t work, go back to the beginning of this section and follow the
steps very carefully. Check that you know where everything is in memory,
both in the system image in low memory and in the running C P /M in high
memory. If your driver worked when you tested it with D D T at location 100,
it should work when installed in the driver.
Good luck!
317
Soul of CP/M®
Once you are able to perform this installation process quickly, this is what
you’ll be typing on the screen. (We’ll assume that you aren’t going to check
memory with DDT.) Of course, the memory size and the number of pages to
SAVE will be different depending on your system.
A>pi p n e w b i o s . a s m = b i o s i o . a s m
A>asm newbios
A>ddt cpm56.com
-inewbios.hex
-r4d80
-gO
A>save 45 cpm56n.com
A>sysgen
A SHORTCUT
Under some special circumstances, you don’t even need to use the assem
bler to modify your BIOS. You can do it directly with DDT, using a process
similar to that previously described, but somewhat shorter. Essentially what
this involves is using the “a” command of D D T to put the code for the new
driver into BIOS, rather than reassemblying the BIOS.ASM file. This could
be useful if the driver you want to install is not too complex, or if you’re
making minor changes in an existing driver, or debugging it.
The limitation of this process is that the new driver must fit into the space
occupied by the old one. Otherwise, when you type in the instructions using
the “a” command, you may be overwriting something im portant. Actually,
you might be able to expand the driver a little, depending on what routine
318
The Innermost Soul of CP/M
follows the driver. If it’s the routine that returns the status of the list device, it
may never be used, so you can overwrite it. Check your BIOS.ASM listing to
see what’s there.
As described in the last section, get your new driver working at 100 hex by
placing a “jm p 100” at the start of printer driver in the running BIOS. Once
it’s working at 100 hex, move it up to high memory with the “m” command
of DDT. For instance, if your routine is 44 hex bytes long and is going to be
installed in location F209, you’d type:
-ml 0 0 , 4 4 , f 209
N ote that you’re directly modifying the operating system that you’re using,
so if you make a mistake, things could die a quick death. You don’t (obvi
ously!) want to use this system to modify the drivers of any peripherals that
are currently in use.
The instructions for your driver are now at the right place in the running
BIOS, but they have the wrong addresses, having been assembled at 100 hex.
Go through the code with the D D T “a” command and change the memory
reference instructions to the correct values. For instance, if there’s a “jm p
100” instruction in the program, you’ll need to change it to “jm p f209” .
Now, turn on your printer. (It should work since it’s running with the new
driver, right?) Use D D T ’s “1” command to dump the code for the driver rou
tine to the printer:
- Lf 209
A>movcpm 64 *
A>save 43 cpm64.com
After that, you can skip this step entirely. Bring this image into memory
with:
A>ddt cpm64.com
Now you have to find the old driver in the image in low memory. You can
either use the magic number N as described earlier or you can simply search
through the code with DDT, looking for the instructions that you know are
in the driver.
Once you find the old driver, you type the new one over the top of it, using
the “ a” command. Type it in exactly as it appears on the printout that you
made above. The memory reference instructions will be wrong for their cur
rent locations in low memory, but they’ll be right later when loaded from the
disk system tracks into high memory.
Save this modified image as CPM64N.COM:
-gO
A>save 43 cpm64n.com
Use SYSGEN to write this new image to the system tracks of a formatted
disk:
A>sysgen cpm64n.com
There you are again! A complete working C P /M system with your own
custom modification. Boot up the new disk and test the driver by using it to
TYPE some long files.
320
The Innermost Soul of CP/M
r
0100 o r g 100h
/
0100 DB23 test in 23h ;ge t p r in t e r status
0102 E685 ani 85h ;mask o f f test b its
0104 EE85 xri 85h ; a r e they a l l on
0106 C20001 jnz test ;no-wai t
0109 79 mov a,c ; g e t c h a r a c t e r i n A - re g
010A FE17 c pi 17h ; i s i t ac o n t r o l - w ?
010C CA1201 jz control ;yes
01 OF C32801 j mp exi t ;no
; i t 1s a c o n t r o l - w
012C end
321
Soul of CP/M®
As an example, let’s take the Epson MX-80. If you send it a control-o (OF
hex), it will start printing in compressed mode. If you send it a control-r (12
hex) it will go back to the normal mode. However, our word-processing pro
gram lets us use only a few control characters for user-defined functions and
neither control-o or control-r are among them. However, control-w is. We’d
like to set things up so that when the driver sees one control-w, it sends a
control-o to turn on the compressed mode, and when it sees the second con
trol-w, it turns off the compressed mode with a control-r.
Listing 9-7 is a new driver program that will do exactly that.
As shown here, it’s ORGed at 100, ready to be checked out by DDT. Once
you know it works, it can then be assembled into BIOS and merged into
CPM64.COM with DDT, as previously described.
322
APPENDIX A
Hexadecimal N otation
BINARY NOTATION
When a computer counts, it starts with 0, like everyone else. It then goes to
one. So far, we can identify with that. But what happens next? The poor thing
has run out of all possible values for this digit! There are only 0 and 1! So it
does just what we do in the decimal system when we run out of digits. It adds
a new column to the left of the one’s column, which gives it 10.
Of course, this 10 isn’t 10 decimal but 10 binary, which is the same as 2
decimal. The column next to the one’s column isn’t the ten’s column, as it is
in decimal notation, it’s the two’s column. (The third column over is the
323
Soul of CP/M®
four’s column, the fourth column is the eight’s column, and so on.) Thus, 11
binary is the same as 3 decimal, and 100 binary is the same as 4 decimal.
Here is a list of the first 16 binary numbers:
decimal, the binary number 1000000 is 128 decimal, and the binary number
10000 is 16 decimal. (See the list of binary to decimal equivalents given later
in this appendix.)
DECIMAL NOTATION
All right, now we know something about binary notation and why com
puters like to use it. But, what does binary have to do with hexadecimal? The
problem is that while computers are happy thinking in binary, the binary
system turns out to be very difficult for humans to read. For example, here’s
the decimal number 48458 expressed in binary: 1011110101001010. Who can
handle this long a string of ones and zeros? How would you like to do arith
metic on such a number, or try to remember it, or write it down accurately?
N ot so easy. Well then, why not just translate each binary number into its
decimal equivalent, and talk about that? The computer can then think in
binary and convert to decimal when it wants to communicate with humans.
Good idea, and one that’s used very often in the computer business. Most
applications programs communicate with the user in decimal notation, and
even whole languages, like BASIC and FORTRAN, are made to speak in
decimal to the user.
However, when we really want to talk about what is going on in the com
puter on a detailed level (as we often do when we write assembly language
programs), the decimal system has certain glaring disadvantages. For one
thing, the decimal number doesn’t tell us anything about how the bits look in
the binary number and, in many programming applications, we need to deal
with patterns of bits. It’s not instantly obvious, for example, that 240 decimal
is 11110000 in binary. Another related problem is that the number of digits
used for a particular binary number has no easy relationship to the number
of digits used in a decimal number. Thus, 1001 and 1010 both have four
digits, but 1001 is 9 decimal (which has one digit), and 1010 is 10 decimal
(which has two digits). This is awkward for printouts and for visualizing
what’s going on.
HEXADECIMAL NOTATION
Fortunately, in the dim and distant past of computer development, some
one came up with a compromise between binary (which is easy for the com
puter to read) and decimal (which is easy for the hum an to read). This com-
325
Soul of CP/M®
promise is called “hexadecimal” notation (or “hex” for short). Where binary
is a number system based on two, and decimal is based on ten, hexadecimal
is based on sixteen. Hexadecimal works by grouping together 4 bits (a nyb
ble) and assigning a symbol to each value that those 4 bits can represent.
Since 4 bits can have any of 16 possible values, we need 16 symbols. It seems
natural to assign 0 (hexadecimal) to the value 0 (decimal), and to assign 1
(hex) to 1 (decimal), and so on, up to 9. But what happens then? We’ve run
out of decimal digits, and we still have six more values to represent. We need
more symbols and they shouldn’t be too obscure, or it will be hard to find
hardware to print them out. Why not use letters of the alphabet? “A”
through “F ” will handle it nicely. This is illustrated in Table A -l.
Now every possible value of a nybble (4 bits) has a unique symbol. Also,
every possible value of a byte (8 bits) can be represented by two of these sym
bols: FF (hex), for example, represents 11111111 binary, or 255 decimal. Also
44 (hex) represents 10001000 binary, or 68 decimal, and so on. Remember the
number 11110000 that we referred to above and which is 240 decimal? It’s
represented by F0 in hex, where the F represents the 1111, and the 0 represents
the 0000. Notice how easy it is to recognize the bit-pattern 11110000 when you
look at “F0.” The hex number lets you “see” the binary number, whereas, the
decimal number doesn’t. Each byte can be uniquely specified by two hex
adecimal symbols, and each symbol represents exactly 4 bits.
326
Hexadecimal Notation
Learning to Think in H ex
Hexadecimal Arithmetic
Let’s try a little arithmetic in hexadecimal just to see what it’s like. For
simplicity at this point, we’ll adopt the convention used in many assemblers
and other programs; an “h” immediately following a number means it’s a hex
number and a “d” immediately following a number means it’s a decimal
number. Thus, 22h is 22 hex, which is 34 decimal, while 22d is 22 decimal,
which is 16 hex.
W hat’s 2h plus 2h? Since both these numbers are the same as their decimal
equivalents and the answer is also the same as its hex equivalent, there’s no
problem. The answer is 4h. Similarly, 4h plus 3h is 7h. W hat about 5h plus
5h? Let’s count on our fingers. While we count out loud, “six, seven, eight,
nine,” we put up one, two, three, and four fingers. Now what? We don’t
count “ten.” Because we’re in hexadecimal, the next digit after nine is “A.”
So, as we put up the fifth finger, we count “A,” and that’s the answer.
What about Ah plus 4h? Let’s count on our fingers again. Starting with
“A,” we’ll count until we have four fingers in the air: “ B, C, D, E.” E is the
answer. To add 7h to Bh, we’d start at “ B” and count until we had seven
fingers in the air: “C, D, E, F, 10, 11, 12.” So the answer is 12h. (Notice how
“F ” in hex is similar to 9 in decimal, in that the next number after it is 10.)
Let’s try adding some 2-digit hex numbers. We’ll write them down in col
umns just as we do when doing arithmetic in decimal notation.
4 B h
+ 1 9 h
6 4 h
327
Soul of CP/M®
Starting with B, we can count 9 more: “C, D, E, F, 10, 11, 12, 13, 14.” So,
Bh plus 9h is 14h. We write down the 4 and carry the 1 to the next column
(the sixteen’s column), where we add 4h and lh and, then, the lh we carried,
giving us 6h. Thus, the answer is 64h.
What about larger hex numbers? Try this:
B C 9 0 h
+ 2 A 2 h
B F 3 2 h
C F 3 4 h
- A 1 6 h
C 5 1 E h
We start by trying to take 6h from 4h, but we can’t, since it’s smaller, so,
just as in decimal subtraction, we borrow one from the next column over.
(This is the sixteen’s column, so we’re really borrowing 16d, which is lOh.)
We add the lOh to the 4h, giving 14h, and 14h less 6h is Eh, as you can prove
by counting backwards on six fingers: “ 13, 12, 11, 10, F, E.” Since we bor
rowed 1 from it, the 3 at the top of the sixteen’s column has been reduced to
2, so when we take 1 from it again, only 1 is left. In the 256’s column, we take
Ah away from Fh, which leaves 5h (counting on five fingers: “ E, D, C, B,
A ”). And, finally, in the 4096’s column, nothing taken away from Ch leaves
Ch. And that’s our answer: C51Eh.
T hat’s not so bad, is it? It took you years to learn decimal arithmetic, and
here you are, learning a whole new numbering system in ju st a few minutes!
Another thing that it is useful to do in hex is being able to recognize the bit
patterns of the sixteen hex digits. A good approach to this is to copy down
328
Hexadecimal Notation
Table A-l on a filing card and tape it up near your computer. If you do a lot
of assembly language programming, you will find that you will refer to it so
much that eventually the bit patterns will become second nature.
The most useful bit patterns to recognize are 0000 binary which, of course,
is Oh, and 1111 binary, which is Fh. Often, a routine will set all the bits in a
section of the computer’s memory to 1 and, then, if you look into memory
with D D T or a similar program, you’ll find it filled with F ’s. Another useful
bit pattern is 7Fh, which is all the bits in a byte being set except the leftmost
one (01111111). This can be used to “mask off” an extraneous upper bit to
obtain the ASCII code for a character. The ASCII character codes for letters
run from 41h to 7Ah, so if you see memory filled with these values, you know
you’re looking at text.
BF 3C
Ch = 12, 12 * 1 12
3h = 3, 3 * 16 = 48
Fh = 15, 15 * 256 = 3840
Bh = 11, 11 * 4096 = 45056
48956
329
Soul of CP/M®
48956 / 4096 = 1 1 , l i d = Bh
remainder = 3900.
3900 / 256 = 15, 15d = Fh
remainder = 60.
60 / 16 = 3, 3d = 3h
remainder = 12.
12 / 1 = 12, 12d = Ch
no remainder.
330
APPENDIX B
U tility Programs
In this appendix, we’ve collected several programs that may prove to be
useful or amusing, but which did not fit into the rest of the book.
HEXDUMP
This program will dum p—that is, print out the contents in hex numbers—
any 128-byte section of memory. It is very similar to the “d” command of
DDT, except that it doesn’t print the ASCII representations of each byte
alongside the hex. This would be easy to add to the program, however, if you
want it.
This program is useful for looking at parts of memory when you don’t
want to use DDT. For example, D D T modifies the page zero addresses when
it’s loaded, so if you want to see what they are in their normal state,
HEXDUM P is just what you want.
331
Soul of CP/M®
; g e t a dd re ss f rom u s e r and i n i t i a l i z e c o u n t s
0100 CD7101 c a ll hexibin ; g e t a dd re ss i n hex
0103 7D mov a ,I ;mask o f f r i g h t - h a n d d i g i t
0104 E6F0 ani Of Oh
0106 6F mov 1/3
0107 0E08 mvi c,8 ; s e t number o f l i n e s
0109 CD2E01 c a ll p c rlf ; p r i n t line fee d
; p r i n t a d d r e ss and s t a r t new l i n e
010C 0610 n uline mvi b, 16 d ; s e t n br o f b y t e s p e r l i n e
010E CD2E01 c a ll p c rlf ; p r i n t linefeed
0111 CD3F01 c a l l phex ; p r i n t a dd re ss
0114 CD3901 c a l l pspac ; p r i n t two spaces
0117 CD3901 c a l l pspac
; p r i n t t he b y t e s f o r t h i s I i n e
011A 56 n u b y t e mov d,m ; g e t byte in t o d
011B CD4801 c a l l pbyte ;p rin t it
0 1 1E CD3901 c a l l pspac ; p r i n t space
0121 23 i nx h ;increm ent hi
0122 05 dcr b ; do ne w i t h t h i s l i n e ?
0123 C21A01 jnz n u b yt e ; not ye t
; do ne t h i s l i n e
0126 0D dcr c ; do ne a 11 I i nes?
0127 C20C01 jnz nuline ; not yet
012A CD2E01 c a l l per I f ; y e s - p r in t lin e fee d
012D C9 ret ; b a c k t o CP/M
; s u b r o u t i n e t o p r i n t a r e t u r n and l i n e f e e d
012E 3E0D p c rlf mvi a,0dh ; p r i n t carriage return
0130 CD6401 c a l l p cha r
0133 3E0A mvi a,0ah ; p r i n t linefeed
0135 CD6401 c a l l p cha r
0138 C9 ret
/ s u b r o u t i n e t o p r i n t a space
0139 3E20 pspac mvi a,20h ; p r i n t space
013B CD6401 c a l l p char
013E C9 ret
332
Utility Programs
; p h e x - r o u t i n e t o p r i n t b i n a r y number i n h i
; o u t on s c re e n i n hex
F
013F 54 phex mov d ,h ; p r i n t 2 d i g i t s f rom H
0140 CD4801 c a l l pbyte
0143 55 mov d , l ; p r i n t 2 d i g i t s f rom L
0144 CD4801 ca 11 p b y t e
0147 C9 ret
w
; subrou t i n e t o p r i n t 2 - d i g i t hex number ( i n d)
0148 7A pbyte mov a , d ; p r in t left-hand d ig it
0149 CD5101 ca 11 p r i n t l
014C 7A mov a , d ; p r i n t right-hand d i g i 1
014D CD5501 ca 11 p r i n t 2
0150 C9 ret
f
; subrou t i ne t o p r i n t one hex d i g i t ( i n a)
0151 07070707 p r i nt1 r I c ! r I c ! rl c ! rIc ! ;move hi 4 b i t s
0155 E60F p r i nt2 ani Of h ; g e t r i d of high 4 b i t s
0157 C630 adi 30h ; ch an ge hex t o ASCII
0159 FE3A cp i 3ah ; i f i t ' s more t han 9
0 15B DA6001 jc pdig ; ( i t ' s not)
015E C607 adi 7h ; add b i a s (A=10, e t c . )
0160 CD6401 pdig ca 11 p c ha r ;p rin t d ig it
0163 C9 ret
;s u b ro u tin e to p r i n t character in a - r e g is t e r
0164 E5C5D5 p c ha r push h! push b! push d! ;save r e g i s t e r s
0167 0E02 mvi c,conout ; p r i n t character
0169 5F mov e , a
016A CD0500 c a l l bdos
016D D1C1E1 pop d! pop b! pop h! ; g e t r e g i s t e r back
0170 C9 ret
*************************************************
h e x i b i n - r e a d s hex number f rom k e y b o a r d ,
stores r e s u lt in b in a ry in hi
333
Soul of CP/M®
017A E1 pop h ; r e s t o r e hi
017B D630 su i 30h ;ASCII d i g i t to b in a r y
017D F8 rm ; return i f <0
017E FEOA cp i 10d ;is i t > 9 ?
0180 FA8B01 jm a dd to ; n o , so i t ' s 0 t o 9
; n o t d i g i t , maybe i t ' s L e t t e r (a t o f )
0183 D627 sui 27h ; c o n v e r t ASCII t o b i n a r y
0185 FEOA cpi Oah ; i s i t < a (hex)
0187 F8 rm ; yes, return
0188 FE10 cp i 10h ; i s i t > f (h ex)
018A F0 rp ; yes, re tu rn
; r o t a t e HL 4 b i t s L e f t and add d i g i t t o l e f t s i d e
018B 57 a dd t o mov d ,a ; s a v e new hex d i g i t i n d
018C 0E04 mvi c,4 ; s e t C to count 4 b i t s
018E 7D s h ift mov a , I ;shi f t I
018F 17 ra I
0190 6F mov I , a
0191 7C mov a ,h ;s h ift h
0192 17 ra L
0193 67 mov h,a
0194 0D dcr c ;do ne 4 b i t s y e t ?
0195 C28E01 jnz s h ift ; not yet
0198 7D mov a , L ;mask o f f Lo 4 b i t s o f L
0199 E6F0 ani OfOh
019B B2 ora d ; " o r " new d i g i t o n t o L
019C 6F mov I,a
019D C37401 jmp newch ;go get next c h a r a c te r
01A0 end
press the key corresponding to the sector that he’s appeared in. If you get the
wrong sector, you lose a point. And, if you’re not fast enough and he gets
through and destroys the earth, you also lose a point. It is only when you’re
fast and accurate that you gain a point.
335
Soul of CP/M®
336
Utility Programs
; w a i t f o r u s e r t o s i g n a l ready
F
0181 02 random db 2
0182 h ol d ds 1
f
337
Soul of CP/M®
01B E 0 D 0 A 0 A 4 1 2 0 m e s s r db c r , I f , Lf,’
A H I T !!!$’
01 CA 0 D 0 A 0 A 5 0 7 2 m e s s j db cr,Lf,Lf,’
Press s p a c e - b a r to start a g a i n .
/
r
HEXIDEC
This program will print out the decimal equivalent to any positive 4-digit hex
number that you type in from the keyboard. It is called directly from CP/M .
■* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; h e x i b i n - r e a d s hex number f rom k e y b o a r d .
r stores r e s u lt in bin ary in hi
F
0001 = coni n equ 1 h
0005 = bdos equ 5h
0030 = ascO equ 30h
000A = dec10 equ 10d
0027 = bia s equ 07h
00F0 = mask equ OfOh
f
010C 210000 h e x i b i n I x i h,0 ;cLear hi
01 OF E5 newch push h ;save hi
338
Utility Programs
; b i n i d e c - c o n v e r t s b i n a r y number i n hL t o
¥ deci m a l , p r i n t s r e s u l t on s cr ee n
W
0002 = conout equ 2 ;console output
0005 = bdos equ 5 ;BD0S e n t r y p o i n t
F
013B 11F0D8 b i n i dec Lxi d , - 10000 ; p r i n t number o f 10, 000s
013E CD5A01 ca L L s u b c n t
0141 1118FC Lxi d , - 1 000 ; p r i n t number o f t housands
0144 CD5A01 ca L L s u b c n t
335
Soul of CP/M®
FILEDUMP
This program (Listing B-4) takes any file and prints out the contents in a
hex format similar to the “d” function of D D T (except that ASCII
equivalents aren’t printed out). Each 128-byte record is shown separately.
Scrolling can be stopped and started with control-s.
This is a useful program for investigating how the records of a particular
file are actually stored on the disk. It is a more convenient version of the
technique that is shown in Chapter 5 for examining the contents of files in
hex, where each record had to be read in separately and examined using
DDT.
340
Utility Programs
; s e t up l o c a l s t a c k
lxi h,0 ;save old stack p o in te r
dad sp
shld old sp
lxi s p , s t k t o p ; lo ad new s t a c k p o i n t e r
r
; op en f i l e , read r e c o r d s
mvi c,openf ; op en f i l e
lx i d , f cb
c a l l bdos
in r a ; ad d 1 t o A - r e g i s t e r . I f i t was f f .
jz n o f i l e ; now i t ' s 0 0 , so no such f i l e
; p r i n t o u t c o n t e n t s o f DMA i n hex f o r m a t
c a ll pbuff
jmp newrec
341
Soul of CP/M®
■ 'k'k'k'k'k'k'k'k'k'kieie'k'k'k'kick'k'icicie'k'k'k'k'kic'kieierk'k'k'k'kie'k-k'k'k'k'ic'kickick'k'k'k'k'k'kic'k'kie
; p r i n t a dd r es s and s t a r t new l i n e
nu I i ne mvi b ,1 6d ; s e t number o f b y t e s p er l i n e
c a l l p er Lf ; p r i n t Linefeed
; p r i n t t he 1b y t e s f o r t h i s l i n e
n u b y te mov d,m ; g e t b y t e p o i n t e d t o by h i i n t o
ca IL p b y t e ;p rin t i t
ca IL pspac ; p r i n t space
i nx h ;increm ent hi
dcr b ; do ne a l l b y t e s on t h i s l i n e ?
jn z nubyte ; not ye t
/
; done t h i s I i ne
dcr c ; do ne a l l l i n e s ?
jnz nu Li ne ; not yet
c a ll p c rlf ;yes - p r i n t r e t u r n & lin e f e e d
ret ; b a c k t o main program
f
; s u b r o u t i ne t o p r i n t a c a r r i a g e r e t u r n and l i n e f e e d
p er Lf mvi a,0dh ; p r i n t carriage return
ca IL pchar
mvi a,0ah ; p r i n t lin e fee d
ca 11 pc ha r
ret
f
; s u b r o u t i ne t o p r i n t a space
pspac mvi a,20h ; p r i n t space
ca IL pchar
ret
342
Utility Programs
**********************************************************
phex - r o u t i n e t o p r i n t b i n a r y number i n h i
o u t on s c r ee n i n hex
343
APPENDIX C
Summary of 8 0 8 0
Instructions
This appendix summarizes the architecture of the 8080 and the 8080
instruction set. Most of the instructions are explained in detail in the main
part of the book. If you don’t have any assembly language programming
experience, don’t read this appendix until you’ve read the first part of this
book. This will avoid the possibility of your being blown away by seeing so
many instructions all at once.
8080 ARCHITECTURE
The diagram shown in Fig. C -l illustrates the principal 8080 registers.
Registers
■16 bits
8 bits 8 bits -
PSW
mov c,m
means, move the contents of the memory location pointed to by the HL-
register into the C-register. There is no “M ” register—it is simply the memory
location pointed to by HL.
The BC- and DE-registers can be used for temporary storage and for a few
arithmetic operations on 16-bit numbers. They can also be used as pointers
to memory, using the LDAX and STAX instructions, although this is not
quite as easy as using the HL-register.
All of the 8-bit registers (A, B, C, D, E, H, and L) can be used for tempo
rary storage of 8-bit data.
346
Summary of 8080 Instructions
The Stack Pointer (SP) holds the 16-bit address that is the current top of
the stack. If you PUSH something onto the stack, it will be placed in memory
at the address in the SP, and if you CALL a subroutine, the address to which
you will RETurn is placed in memory at this address. When something is
placed on the stack (by a PUSH or a CALL) the SP is automatically decre
mented two locations (since two bytes hold a 16-bit address), and when some
thing is taken off the stack (by a POP or a RET), the SP is incremented. This
is the opposite of what you might expect because the stack grows downward
in memory.
In addition to the registers shown in Fig. C -l, there is also a 16-bit Pro
gram Counter (PC) (which holds the address of the instruction currently
being executed) and an 8-bit Instruction register (I) (which holds the instruc
tion being executed). These registers are seldom accessed in normal 8080 pro
gramming.
The Program Status Word contains the five “flags” used in the 8080
processor. These flags are set, following the completion of some instructions
(mostly 8-bit arithmetic instructions), to indicate the outcome of different
operations. They can have either of two values, 1 or 0. Jumps, calls, and
returns can sample these values automatically to decide what to do. (For
example, JZ will cause a jum p if the zero flag is set, but not if it isn’t.)
The flag bits, and the mnemonic associated with each value, is shown in
Table C-l. The sign flag is used to indicate whether the result of an arithmetic
instruction is plus or minus (positive or negative). The Zero flag indicates
whether the result of an operation is zero or nonzero. The Auxiliary Carry flag
indicates whether there has been a carry from Bit 3 to Bit 4 in an arithmetic
operation. Its primary use is in BCD (binary-coded decimal) arithmetic.
347
Soul of CP/M®
8080 INSTRUCTIONS
1. 8-bit Transfers.
2. 16-bit Transfers.
3. 8-bit Arithmetic.
4. 16-bit Arithmetic.
5. Jumps, Calls, and Returns.
6. Rotates.
7. Other instructions.
course, for this to work, you must put the appropriate address in the HL-
register before executing an instruction with an “m” as an operand.
Also remember: numbers used in an ASM listing are assumed to be in deci
mal unless otherwise specified. If a number is meant as a hex number, it must
be followed by an “h,” as in Offh. Also, hex numbers starting with a letter
must be preceded by a zero, or ASM will think they’re a name. For clarity,
it’s probaby best to put a “d” after decimal numbers as well, although this is
optional. Numbers followed by “b” are assumed to be in binary notation, as
in 11110000b.
8-Bit Transfers
MVI R,data Puts an 8-bit data byte into R, where R can be any one of the
registers A, B, C, D, E, H, L, and M. (The data are stored in
the byte immediately following the MVI instruction.)
Examples:
mvi d,31
mvi b,const (where “const” is given a numerical value
in an EQU statement)
LDA Addr Puts an 8-bit byte, located in memory address Addr, into the
A-register.
Examples:
Ida 3c00h
Ida const
STA Addr Stores an 8-bit byte from the A-register into memory address
Addr.
Examples:
sta eOlOh
sta temp
LDAX RR Loads the A-register with 8-bit data contained in memory loca
tion that is pointed to by register RR, where RR is either “b”
or “d,” meaning the BC- or DE-register.
349
Soul of CP/M®
Example:
ldax b
STAX RR Stores an 8-bit value from the A-register into memory location
that is pointed to by register RR, where RR is either “b ” or
“d,” meaning the BC- or DE-register.
Example:
stax d
16-Bit Transfers
LHLD Addr Loads the HL-register with a 16-bit value that is stored in
memory addresses A ddr and A ddr + 1.
Example:
lhld pointr
SH LD Addr Stores a 16-bit value from the HL-register into memory loca
tions A ddr and A ddr-f 1.
Example:
shld 0bd80h
LXI RR,data Loads a 16-bit data word into register RR, where R R can be
“b,” “d,” “h,” or “sp,” meaning BC, DE, HL, or SP. The data
are in the two bytes immediately following the instruction.
Examples:
lxi d,0005h
lxi h,ptrl
PU SH RR Puts the 16-bit contents of register RR onto the top of the
stack, where RR can be “b,” “d,” “h,” or “psw,” meaning BC,
DE, HL, or the register-pair formed by the A-register and the
PSW. It decrements the stack pointer twice.
Example:
push h
PO P RR Takes the 16-bit value from the top of stack and puts it in reg
ister R R (where R R is defined as in PUSH). It increments the
stack pointer twice.
Example:
pop b
XTHL Exchanges the two 16-bit values in the HL-register and the top
of the stack.
350
Summary of 8080 Instructions
Example:
xthl
SPH L Loads the 16-bit contents of the HL-register into the stack
pointer (SP) register.
Example:
sphl
XCHG Exchanges the two 16-bit values in the HL-register and the
DE-register.
Example:
xchg
8-Bit Arithmetic
Examples:
add b
cmp c
16-Bit Arithmetic
The JMP, CALL, and RET instructions all have variants that only execute
when a particular condition is true. Thus,
jz start
will only transfer control of the program to “start” if the results of a previous
arithmetic operation left the zero flag set to 1. If the condition is not met,
control simply passes to the next instruction in line. Rather than list all of
these instructions separately, we’ve arranged them in a table (Table C-2). The
most-used of these instructions are probably JNZ, JZ, JNC, JC, JP, and JM.
Instruction executes
if flag set to JU M P CALL RETURN
Nonzero JNZ CNZ RNZ
Zero JZ CZ RZ
N o Carry JNC CNC R NC
Carry JC CC RC
Parity Odd JPO CPO RPO
Parity Even JPE CPE RPE
Plus JP CP RP
Minus JM CM RM
Rotations
All rotations are done on the contents of the A-register. The A-register can
be rotated either right or left, and the bits pushed off the end can either circle
round and appear on the other end of the A-register, or they can go to the
carry bit of the program status word. The diagrams shown in Fig. C-2 illus
trate the various possibilities.
353
Soul of CP/M®
/ 7 6 5 4 3 2 1 0
/l
H 3 Carry
I I A-reg ster
Bit
Example: ral
7 6 5 4 3 2 1 0
/ yi
ii
i !
i
____ I____
A-register
! I :T>
Carry
Bit
Example: rar
0 _L .........
Carry A-register
Bit
Example: rlc
7 6 5 4 3 2 1 0
/ m A
i------- ►
i i i : i i i
W - - G )
A-register Carry
Bit
Example: rrc
O ther Instructions
IN Inputs an 8-bit byte of data into the accumulator from the data port
specified in the instruction. The data ports are numbered from 1 to FF
(hex).
Example:
in 23h
OUT Outputs an 8-bit byte of data from the A-register to the data port
specified in the instruction.
Example:
out p tr (where “p tr” is defined elsewhere).
There are several instructions that you can use to alter the bit in the carry
flag:
CMC Complements the carry flag. That is, it changes it to 0 if it was 1, and
vice versa.
Example:
cmc
STC Sets the carry flag to 1, no m atter what it was before.
Example:
stc
You can also complement the A-register; that is, change every bit that’s a 1
to a 0, and vice versa.
You can halt the operation of the computer and you can also have an
instruction which does nothing.
HLT Halts the computer, which is then paralyzed, unless there’s an inter
rupt. N ot to be used frivolously.
Example:
hit
NOP Means “N o Operation.” It can be used as a “filler” in a program.
Example:
nop
And, finally, there are several instructions connected with the use of the
interrupt system. Since we are not concerned with the use of interrupts in this
book, we won’t attem pt to explain them. However, they are:
The instruction RST (which we use when returning from a program we’ve
written in DDT) was designed for use in the interrupt system. However, it
can be executed by any program. There are 8 such instructions: RST 0
through RST 7. They are each the equivalent of a CALL instruction, but they
transfer control only to certain predefined locations in memory.
ASSEMBLER DIRECTIVES
Assembler directives are placed in the instruction field of an ASM listing
as if they were 8080 instructions, but they’re not. They’re instructions to the
ASM program itself. For this reason, they’re sometimes called “pseudo-ops.”
ORG Causes ASM to start assemblying at the memory location given in the
address field.
Example:
org lOOh (causes assembly to start at location 100 hex)
END Causes the assembly to end. Any subsequent statements will not be
processed. If followed by an address, this address will be the starting
address used in HEX files.
Example:
finish end lOOh
EQU Defines a symbolic label as having a specific value. N ote that this di
rective does not set aside any memory to hold the value, it simply
causes ASM to remember what value is associated with a label.
Example:
bdos equ 5h (defines the label bdos to have the value of 5
hex)
Example:
lnfeed equ Oah (defines the label lnfeed to have the value of
0a hex)
357
Soul of CP/M®
DB Means “define bytes.” This directive is used to set a single 8-bit byte
or strings of bytes in memory to a specific value. A number of bytes
can be placed in the same db directive by separating them with com
mas. A string of ASCII characters can be used if it starts and ends with
single quotes (’). Symbolic values (labels) can be used to represent
bytes if they are defined elsewhere.
Examples:
decten db lOd
crlf db Odh, Oah
messl db ’Type your name ’,Odh,Oah
mess2 db ’What?’,cr,lf
DW This directive is similar to DB, except that it defines 16-bit words. A
number of words can be used if they are set off with commas, and
ASCII characters can be used if they are delimited by single quotes.
However, strings must be limited to 2 characters.
Examples:
mask dw OffOOh
data dw 0d4h, 63h, 7dh, 0c9h
crlf dw OdOah
abee dw ’ab’
DS This directive is used to set aside an area of memory for storage of
bytes or groups of bytes. The number given in the address field is the
number of bytes to be set aside. The memory locations thus reserved
are filled with zeros by the assembler.
Examples:
temp ds 2 (sets aside two-byte word)
stemp ds 1 (sets aside 1 byte)
buffer ds 400d (sets aside 400 bytes)
358
APPENDIX D
Tables
This appendix contains the following charts and tables:
H ex ASCII
07 bell (or beep)
09 tab
0A linefeed
0D carriage return
359
Soul of CP/M®
HEXADECIMAL-TO-DECIMAL CONVERSION
On the next page, we furnish an easy-to-use listing that will permit you to
make fast and accurate conversions between hex and decimal notations, and
vice versa.
360
Tables
361
Soul of CP/M®
K Decimal H ex H ex -1
IK 1024 400 3FF
2K 2048 800 7FF
4K 4096 1000 F FF
8K 8192 2000 1FFF
16K 16384 4000 3FFF
20K 20480 5000 4F F F
32K 32768 8000 7F FF
48K 49152 cooo BFFF
56K. 57344 E000 D FFF
64 K. 65536 10000 F FF F
362
Tables
363
APPENDIX E
Function
Number in Refer to
C-register Value passed Value returned Typical calling page
D ec H ex Function to function by function sequence number
0 00 System Reset N one N on e mvi c,0 68
call 5
1 01 Console Input N one A = ASCII mvi c,l 63
character call 5
2 02 Console Output E = ASCII N on e mvi c,2 32
character mvi e,65
call 5
3 03 Reader Input N one A = ASCII mvi c,3 96
character call 5
4 04 Punch Output E = ASCII N on e mvi c,4 98
character mvi e,65
call 5
365
Soul of CP/M®
Function
Number in Refer to
C-register Value passed Typical calling page
D ec H ex Function to function by function sequence number
5 05 List Output E = ASCII N on e mvi c,5 93
(Printer) character mvi e,65
call 5
6 06 Direct Console E = ff (hex) A = 00 or mvi c,6 87
I/O (Input) A = ASCII mvi e,ff
character call 5
(note 1)
Direct Console E = ASCII N on e mvi c,6
I/O (Output) character mvi e,65
call 5
7 07 G et I /O Byte N one A = I/O mvi c,7 98
byte value call 5
8 08 Set I/O Byte E = I/O N on e mvi c,8 105
byte value mvi e,D 2
call 5
9 09 Print String (see D E = string N on e mvi c,9 72
note 2) address lxi d,200
call 5
10 0A Read Console D E = buffer Characters in mvi c,a 76
Buffer address buffer lxi e,300
call 5
11 OB G et Console N one A = F F (hex) mvi c,b 45
Status or A = 00 call 5
12 0C Return Version N one HL = version mvi c,c *
Number number call 5
13 0D Reset D isk System N one N on e mvi c,d *
call 5
14 0E Select Disk E = disk N one mvi c,e *
drive number mvi e,2
call 5
15 OF Open File D E = FCB A = directory mvi c,f 142
address code (note 3) lxi d,5c
call 5
16 10 C lose File D E = FCB A = directory mvi c,10 173
address code (note 3) lxi d,5c
call 5
17 11 Search For First D E = FCB A = directory mvi c, 11 210
address code (note 3) lxi d,5c
call 5
366
Summary of BDOS System Calls (For CP/M 2.2)
Function
Number in Refer to
C-register Value passed Value returned Typical calling page
D ec H ex Function to function by function sequence number
18 12 Search For N ext D E = FCB A = directory mvi c,12 214
address code (note 3) lxi d,5c
call 5
19 13 D elete File (note 5) D E = FCB A = directory mvi c,13 182
address code (note 3) lxi d,5c
call 5
20 14 Read Sequential D E = FCB A = directory mvi c,14 147
Record address code (note 4) lxi d,5c
call 5
21 15 Write Sequential D E = FCB A = directory mvi c,15 171
Record address code (note 4) lxi d,5c
call 5
22 16 Make File D E = FCB A = directory mvi c,16 170
address code (note 4) lxi d,5c
call 5
23 17 Rename File D E = FCB A = directory mvi c,17 *
address code (note 3) lxi d,5c
call 5
24 18 Return Login N one HL - login mvi c,18 *
Vector vector call 18
25 19 Return Current N one A = current mvi c,19 *
D isk disk drive call 5
26 1A Set D M A Address D E = new N on e mvi c ,la 149
D M A address lxi d,80
call 5
27 IB Get Allocation N one HL = alloca mvi c ,lb *
Address tion address call 5
28 1C Write Protect Disk N on e N on e mvi c ,lc *
call 5
29 ID Get R ead/O nly N one HL = read/ mvi c ,ld *
Vector only vector call 5
30 IE Set File Attributes D E = FCB A = directory mvi c ,le *
address code (note 3) lxi d,5c
call 5
31 IF Get D isk Para N one HL = disk mvi c ,lf *
meters Address parameters call 5
address
367
Soul of CP/M®
Function
Number in Refer to
C-register Value passed Value returned Typical calling page
D ec H ex Function to function by function sequence number
32 20 Set User Code E = user N on e mvi c,20 *
code (0 to 31) mvi e,l
call 5
Get User Code E = FF A = user mvi c,20
code call 5
33 21 Read Random D E = FCB A = error mvi c,21 185
Record address code (note 6) lxi d,5c
call 5
34 22 Write Random D E = FCB A - error mvi c,22 187
Record address code (note 6) lxi d,5c
call 5
35 23 Compute File Size D E = FCB bits rO, r l, r2 mvi c,23 196
address in FCB set to lxi d,5c
file size call 5
36 24 Set Random D E = FCB bits rO, r l, r2 mvi c,24 197
address in FCB set to lxi d,5c
record number call 5
NOTES: 1. On Input, AA = 0 means no character is ready yet. Direct console I/O does not
recognize the normal control codes (AP, AS, etc.)
2. The string must terminate with a “$” character.
3. Directory Code (I)
= FF if file cannot be found.
= 0, 1, 2, or 3 if file found. Number corresponds to position o f file entry in
directory page.
4. Directory Code (II)
= 00 if operation was successful.
= nonzero if end-of-file (reading), disk full (writing), or directory full (make
file).
5. Wildcards (“?” characters) can be used in the program name.
6. Error codes returned in random record operations:
00 Operation successful.
01 Reading unwritten data (read only).
02 (Unused).
03 Cannot close current extent.
04 Seek to unwritten extent (read only).
05 Directory overflow opening new extent (write only).
06 Seek past physical end of disk.
7. The descriptions of the Functions marked with an asterisk (*) are not included
in this book.
368
APPENDIX F
Summary of DDT
Commands
This appendix summarizes the commands used in DDT. Except for the
first section, which is about loading DDT, they are arranged in alphabetical
order: A, D, F, G, H, I, L, M, R, S, T, U, and X.
LOADING DDT
A>ddt
A COM or HEX program can be loaded into memory from the disk at the
same time that D D T is loaded, by appending the program name to “d d t” .
(Different file extensions can be used for COM files, like “d d t” .)
A>ddt t e s t p r o g . c o m
A>ddt t e s t 1 1 0 . d d t
D D T loads itself into the CCP area of high memory and also uses various
parts of page zero. It loads the COM or HEX program into the TPA, starting
at address 100 hex for COM files, and, at the starting address specified by the
program, for HEX files.
369
Soul of CP/M®
This command lets you type in programs in a symbolic format; that is,
instructions mnemonics may be used, but not labels.
From DDT, type “a” followed by the address (in hex) where you want to
start the assembly:
- a 100
or
-a20B0
100 mvi c , 0
102 ca ll 5
105 ret
106 —
---------------------------- Type a “return” when you’re done.
-d400
0400 41 0A 00 00 00 DE DO 00 01 B7 D8 AC 7E E9 42 43 A....................... ~.BC
ASCII equivalents to the hex values are printed on the right, with periods
used for nonprintable values.
If no ending address is specified, 16 lines of 16 bytes each are displayed.
An ending address can also be used, following the starting address:
370
Summary of DDT Commands
-d400,42f
This will cause the dump to stop when the ending address is reached (in this
case, after 3 lines).
If neither a starting nor an ending address is typed, 16 lines will be
dumped starting at the last address used (initially 100, when D D T is loaded).
-f4 00 ,5 00 ,ff
will fill the block of memory from 400 to 500 with the value ff. The constant
must be a one-byte value—in the range 0 to ff hex.
“G” FOR GO
This command is used to transfer control to a program at a particular
address in m em ory-in other words, to execute the program. The usual form
is “g” followed by the starting address:
-g100
which will cause the program that starts at 100 hex to be executed. It is simi
lar to a jum p instruction in assembly language, or a GOTO in BASIC. A “g”
to location 0 brings you back to C P /M (with a cold boot):
-g0
A>
Note that the only way to get back to D D T once you’ve used “g” is is to
have your program end with an “RST 7” instruction (or you can use
breakpoints).
You can also use the “g” command to set “breakpoints,” which are tempo
rary jum ps back to D D T that are inserted automatically in your program and
371
Soul of CP/M®
then deleted once they’ve been used. Breakpoints are useful if you want to
execute only a portion of a program that you’re debugging. You insert a
breakpoint at the end of the section you want to execute, and D D T will
regain control from your program at that point, leaving your program
unchanged in memory. Thus, only the section of your program between the
starting address (the first address specified in the “g” command) and the
breakpoint (the second address specified) will be executed.
Either one or two breakpoint addresses can be typed following the starting
address:
“I” COMMAND
«¥
L” FOR LIST
You can also specify an ending address, in which case, the last address
displayed will be the one you specify:
-1100,116
-I
- m 10 0 ,1 2 4, 2 00
Before this command is used, the “I” command is used to insert the name
of the COM or HEX program into the File Control Block at 5C hex. To do
this, simply type the name of the program, along with its file type, following
- i testprog.hex
Now to actually read the program into the TPA, type “r” :
-r
COM files will be loaded at 100 hex and HEX files will be loaded at the
address that you specified in the “end” statement of the ASM file when you
created the program. Make sure the program does not overwrite the zero
page (0 to ff hex), since D D T uses this area.
Note that using “i” and “r” together like this has the same effect as loading
in the COM or HEX program when you first load DDT:
A>ddt t e s t p r o g . h e x
is the same as
A>ddt
- i t e s t p r o g . hex
-r
An optional offset can also be used with the “R” command. This causes
the program to be loaded into a different address than the one that it nor
mally would be; that is, 100 hex for COM files and the address specified in
the ASM file’s “end” statement for HEX files. The new address is found by
adding the offset to the normal ad d ress-th at is, the offset is the difference
between the new address and the normal one. To use this option, type the
offset after the “r” :
-r40
If, in this case, a COM file had already been specified by the “I” com
mand, the program would be loaded at 140, since 100 (the normal loading
address of COM files) plus 40 equals 140.
374
Summary of DDT Commands
- s 1 10
110 B5
You can now type in a new value or, if you want to leave this address
unchanged, you can simply press “return” to go on to the next address. A
period is used to terminate the function.
- s 1 10
110 B5 cO
111 00 b6
112 00 Nothing is inserted here; “return” leaves current value unchanged.
113 2D c9
114 76 0
114 7 F Type period to terminate function.
375
Soul of CP/M®
This command is very similar to trace, except that nothing is printed out.
You simply type “u” and the number (in hex) of the instructions you want
D D T to execute:
-u10
Thus, if you know, for example, that your program executes a loop of say,
10 instructions correctly five times, but does something incorrect on the sixth
iteration of the loop, you can say:
It’s often helpful to be able to look a t—and alter—the contents of the vari
ous 8080 registers when you’re trying to debug a program. The “x” command
lets you do both these things. You can also examine and change the contents
of the flags. To do this, you type “x” followed by a one-letter mnemonic for
the register that you want to look at or modify. For instance,
Sometimes you have to type a 4-digit hex number, as in the case of the
registers BC, DE, HL, the program counter P, and the stack pointer S. For
the A-register, you must type a 2-digit number and, for the flags, you must
type a single binary digit: 0 or 1.
Here’s a list of the mnemonics used for the various registers, together with
the range of values that can be put into them:
A A-register 0 to FF
B BC-register 0 to FFFF
D DE-register 0 to F F FF
H HL-register 0 to F FFF
S Stack Pointer 0 to FF FF
P Program Counter 0 to FF FF
M Minus Flag 0 to 1
C Carry Flag 0 to 1
Z Zero Flag 0 to 1
E Even Parity Flag 0 to 1
I Interdigit (Auxiliary) Carry Flag 0 to 1
377
Soul of CP/M®
Notice that if you want to put just a 2-digit (8 bit) number into, say, the
B-register, you must put an entire 4-digit (16 bit) number into the BC-regis
ter. W ith the “x” command, there is no way to work with the B, C, D, E, H,
or L registers as separate 8-bit registers.
378
APPENDIX G
Summaries of Programs
Used and Locations of
Instruction Descriptions
PROGRAMS USED
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
Chapter 7
Chapter 8
DESCRIPTIONS OF INSTRUCTIONS
Chapter 2
Chapter 3
Chapter 4
RM Decibin Routine
RP Decibin Routine
SUI Decibin Routine
DAD Decibin Routine
Chapter 5
Chapter 7
Chapter 9
IN Printer Driver
OUT Printer Driver
XRI Printer Driver
382
Index
A A SM —cont.
and D D T , format differences between,
“A ” command, 109 111-112
explained, 38 assembler, 108, 109-116, 136
Add Immediate instruction; see A D I file(s), 110, 118, 223, 283
instruction defined Assembler
Addresses, reverse order, 43, 114 D D T mini, 29
A D I instruction defined, 104 directives, 357
A-L using the, 107-136
program, 244-248 Assem bly language
routine, 248-277 8080, 136
getting back to BASIC from, instructions, 348
249 routine, 243, 246, 277, 325
getting from BASIC to, 249 Assemblying
how do we pass arguments between and using DECIH EX, 134
BASIC and, 249-251 a program, 110, 113-115
m oving after loading, 256
operating on strings with, 274-276
passing arguments to BASIC from, B
271-274
POKEing into memory, 262-267 Barber-Pole display program, 51-63
putting into memory, 255-271 BASIC, 243, 244, 248, 262-267
transferring control between BASIC using system calls from, 243-277
and, 248-249 Basic
using a BASIC program to load the, disk operating system; see BDOS
267-271 input/output system; see BIOS
Allocation BC-register, 27
units, 138-139, 170, 202, 205, 207-209, BDOS, 22, 143, 17 0 -1 7 1 , 182, 185, 207,
221 221-222, 310;
vector; see bit map see also basic disk operating sys
A n d logical operation, 102-103 tem
A N I instruction defined, 102-103 system calls, summary of, 365-368
A-register, 27, 129 talking to, 139-142
Arguments, 243, 249-251, 271-274 BEEP program, 64-66
ASCII, 72, 78, 105, 123, 130, 133, 155, Bias or offset, 314
159, 213, 267, 359-360 Binary, 116, 129
character set, 51 decimal, and hex conversion, 363
defined, 25 notation explained, 323-325
ASM , 14, 15, 108, 153, 248 numbers, 29
383
Soul of CP/M®
384
Index
D ata—cont. D isk—cont.
transfers, memory to memory, 236-240 operating system, C P /M , 167
DB directive defined, 155-156 system calls, 137-167
DBASE, 245-247, 251-252, 263 utility program, 202
dBASE II, 243 writing to, 169-199
“D ” command, 81, 109 DMA
explained, 73 address, 141
D C R instruction defined, 85-86 buffer, 140, 146, 149, 150, 172, 175, 180,
D D T , 14, 107-109, 136, 143-147, 212, 234, 188, 194-196, 215-216
245, 247, 248-254, 320, 331, 340 dilemma, 146-147, 149
and ASM , format differences between, location problem, 146-147
111-112 “$” label, 257-258
commands, summary of, 369-378 D ollar-sign label, 256, 258; see also “$ ”
hex arithmetic function, 312 label
loading, 369 Drive
-the programmer’s x ray and probe, 2 9 - code, 207, 210
30 default, 145
typing in number, 141
a message with, 73-75 Driver
the program using, 39-41 installation steps, quick summary of,
D EC IBIN routine, 110, 116, 118-120, 318
122-123, 196 into the C P /M system, inserting new,
assemblying and executing, 125-126 314-318
D EC IH EX program, 126, 130-132 in your BIOS, installing, 308-314
Decimal routines, 281, 314
converting hex to, 329 testing the, 305-307
hex, and binary conversion, 363 writing a sample, 303-306
notation explained, 325 D ummy argument, 254
-to-binary conversion, 116-118 D W directive defined, 160-163
-to-hexadecimal conversion routine,
126-130
to hex, converting, 330 E
D E F IN T statement, 252
D E F U S R statement, 249, 270, 271 8-bit
D elete File system call, 169, 182-183 arithmetic, 351-252
DE-register, 27, 77 transfers, 349-350
Descripter, 274-275 8080
D IR, 216 8080A, 8085 microprocessor chips, 13
D IRECT CONSOLE I/O system program, architecture, 24-28, 345-348
87-93 assembly language, 136
Directory, 219-220 instructions, 345, 348-357
code, 212; see also return code microprocessor, 27, 289
disk, 205, 209-210 register, 234
scanning the entire, 216 E5, 210, 217, 219
Disk ECHO program, 82-83
directory, 144, 174, 202, 205, 209-210 Editing commands, 76-77
and wildcards, 201-242 C P /M ’s built-in, 76
385
Soul of CP/M®
End File(s)—cont.
directive, 132 erased, 217
-of-file; see EOF getting the vital information about the,
marker, 148 219-220
statement, 120 merging, 310
EOF, 153, 155, 180 on the disk, how C P /M stores, 202-210
EQU directive, 180, 258 opening, 205
defined, 132-133 outputting in hex, 341-343
Erased files storage, 340
saving, 218-221, 222 writing to a new, 188-189
using E5 with, 210, 217 F IL E D U M P program, 340-343
E T X /A C K protocol, 303 F lag(s), 124, 353
Examining the FCB, 206 list of, 347-348
Exclamation point directive defined, Floating Point Accumulator, 250; see also
129 FAC
Exclusive-OR, 292 Flowchart defined, 53
Executing programs from Format differences between D D T and
C P /M , 66-68 ASM , 111-112
D D T , 42 FO R TR A N , 243
Existing record, writing to, 189 Full disk operating system; see FDO S
Extents, 138-139, 173, 186, 202, 205 Function byte, 292
opening new, 207, 208, 211
G
F
“G ” comm and explained, 41
FAC, 250-251, 253, 273, 274 Get
FBASE, 245, 262-263, 264 Console Status, 52
FCB, 140, 142-144, 148, 170-172, 185, system call defined, 45
195-196, 205, 207, 316; I/O Byte system call, 98-105
see also file control block Golden Rule, C P /M ’s, 21
creating new, 220
examining the, 206
FDO S, 23, 68 H
Field(s)
comment, 113 Hex
label, 113 arithmetic function, 312
operand, 34 converting decimal to, 329-330
operation, 34 decimal, and binary conversion, 363
statement, 33-36 files, 110, 115, 118, 120, 248, 267-271
File(s), 148, 211-212 -to-decim al conversion program, 3 3 9 -
ASM , 223 340
control block, 140-142, 203-205; see Hexadecimal, 112, 116
also FCB arithmetic, 327-328
count words in, 222-242 bit patterns, 329
close the, 220-221 digits, 127
defined, 139 instruction codes, 35
386
Index
387
Soul of CP/M®
388
Index
389
Soul of CP/M®
STORE U
program, 171, 177-182, 190, 196, 197,
219 U A R T , 283, 285, 290, 292, 293, 305
modifying, 182 U C A SE program, 275-276
text in file, 177 Universal Asynchronous R eceiver/Trans
String(s) mitter; see U A R T
argument in BASIC, 274 U se factor, 114
defined, 72 U sing
-handling system calls, 82 a BASIC program to load the A-L rou
in D B directives, 157 tine, 267-271
operating on with an A-L routine, 2 7 4 - the L O A D program, 115
276 U SR function, 249, 250, 275
SU BM IT utility, using C P /M ’s, 134-136 U tility
Subroutines, 36, 120-125 program(s), 331-343
in W ORDS, 242 disk, 202
SUI instruction defined, 121-122
Symbolic labels, 112-113, 195
SY SG E N utility, 317, 320
W
System
calls, 12, 19-20, 31-106, 223
console, 31-106 Warm boot, 41, 66, 68-69, 221, 222, 293
disk, 137-167 Wildcards, 211-213, 215, 222
nondisk, 106 and the disk directory, 201-242
string-handling, 82 how W O R D S handles, 240-241
summary o f BDOS, 365-368 Word processor, using the, 110
Reset system call defined, 68 WORDS
tracks, 317 program, 199, 202, 216, 222-242
subroutines in, 242
Write
Random system call, 187-189
T Record system call, 171, 182
Sequential Record system call, 169, 170,
171-173, 183
Talking to BDOS, 139-142
Writing
Text editor, your own, 182
a sample driver, 303-306
TPA, 21, 68; see also transient program
to a new file, 188-189
area
to existing record, 189
Tracks, 138-139, 202, 203, 205
Transferring control between BASIC and
the A-L routine, 248-249
Transient program area; see TPA X
Transportability
machine, 20 “X ”
program, 18 command explained, 126
TYPE2 program, 153-157 used in instructions, 74
Typing in X C H G instruction, 240, 275-276
a message with D D T , 73-75 X O N /X O F F protocol, 302, 304, 305
the program using D D T , 39—41 X RI instruction defined, 291-293
390
Index
391
TO THE READER
S am s C o m p u ter books cover F u n d am en tals — Program m ing — In terfac in g — Techn olo gy w ritte n to m eet the
needs of co m p uter engineers, profession als, sc ientists, te chn icians, students, educators, business ow ners, per
sonal co m p u te ris ts and hom e hobbyists.
Our Tradition is to meet your needs and in so doing we invite you to tell us what
your needs and interests are by completing the following:
3 . M y occupation is:
______ S cien tis t, E ngineer ______ D P P rofessional
N am e (print)_______________________________
A ddress___________________________________
C i t y ______________________________________ S t a t e _____________________Zip
SAMS.
PRODUCT NO. QUANTITY PRICE TOTAL
N a m e (p le a s e print)
S ig n a tu re __________
Address __________
C ity _______________
S ta te /Z ip ________________________________
D Check _ - :t
S ubtotal □ VISA Z V c r e - lD - :
A ccoun t N um b er
AR, CA, FL, IN, NC, NH, OH, TN, WV
residents add local sales tax
_
H a n d lin g C harge $2.50 E xpiratio n Date _____________________________ __
O ffe r g o o d m U SA o n ly P r< e s s o f t e r »o c*xx» g e « nt+ vxj* n o tic e
WC020 Total A m o u n t Enclosed F u ll p a y m e n t m u s t o c c o m p o n y y o u r or&e<
More Books from Sams and The Waite Group
□ C Primer Plus □ Artificial Intelligence Programming on
P rovides a clear and com plete introduction to the C the Macintosh™
program m ing language. This well illustrated primer Includes tutorials in Logo as well as in Lisp and
guides you in the proper use of C program m ing Prolog, the three main A I langu ages. For
m ethodology and interfacing C w ith assem bly program m ers w hose background is in B A SIC , an
language. W aite, Prata, and Martin. appendix show s how to convert the program exam ples
No. 22090, $22.95 to th a t language. D an Shafer.
No. 22447, $24.95
UNIX™ Primer Plus
P resents the elem ents of U N IX clearly, sim ply, and CP/M ® Primer (2nd Edition)
accurately, for ready understanding by anyone in any C om pletely updated to give you the know-how to
field who needs to learn, use, or work w ith U N IX in begin working with new or old CP/M versions
some way. Fully illustrated. W aite, Martin, and Prata. im m ediately. Includes CP/M term inology, operation,
No. 22028, $19.95 capabilities, internal structure, and more.
W aite and Murtha.
U N IX SYSTE M V Primer No. 22170, $16.95
Show s you how to work w ith multi-user, m ulti-tasking
U N I X S ystem V, including its built-in u tilities and CP/M Bible: The A u thoritative Reference
softw are tools. Reference cards included. Guide to CP/M
W aite, Martin, and Prata. G ives you instan t, one-stop access to all CP/M
No. 22404, $19.95 keywords, com m ands, utilities, conventions, and more.
A m ust for any com puterist usin g any version of
A dvanced UNIX — A Programmer's Guide CP/M. W aite and Angermeyer.
U se U N IX in problem -solving. Learn to develop No. 22015, $19.95
shells, use U N IX tools, functions, and U N I X graphics,
and use C w ith U N IX . E xercises and qu estion s test Soul of CP/M: How to Use the Hidden
your progress. Stephen Prata. Power o f Your CP/M System
No. 22403, $17.95 T eaches you how to use and m odify CP/M ’s internal
features, use CP/M sy stem calls, and more. You'll
□ The UNIX™ Shell Programming need to read C P /M P R IM E R or be otherw ise familiar
Language with CP/M 's outer-layer utilities. W aite and Lafore.
An advanced program m ing guide em phasizing the No. 22030, $19.95
Bourne shell, while including the C shell and the
Korn shell as well. This book dem onstrates how the Discovering MS™-DOS®
pow erful U N IX shell program m ing language is From com plete description of the basic com mand set
creating a revolution in program ming. M any easy-to- through analysis of architectural im plications, you will
use exam ple program s can be run on any com puter. gain a com plete understanding of this operating
environm ent. K ate O'Day.
M anis and Meyer.
No. 22407, $15.95
No. 22497, $24.95
PLACE
STAMP
HERE
Com m ents.
o n Oo
o O Z)
"D rn
Cn
2
Q * n
-I CO
rri
3 rri
CD
3
-
e o
y*
Z
a
5MW5
SAMS
T ftć A f/aćtŁ
Soul of CP/M*
How to Use the Hidden Power of Your CP/M System
Do you w a n t to le a rn the u niversal "system ca lls" th a t m a ke C P /M the most p o p u la r
o p e ra tin g system in the m icro co m p u te r w o rld ? W o u ld yo u lik e to k n o w h o w to pro g ra m
in 8080 assem bly la n g u a g e ? If so, read this bo o k! Using a u n iq u e a p p ro a ch , it teaches
you both C P /M system calls and 8080 assem bly la n g u a g e at the sam e tim e !
Y our vo ya g e o f discovery w ill ta ke you d e e p inside C P /M to its Soul. Y o u 'll d iscover h o w
to m o d ify BIOS so y o u r C P /M system w ill run w ith d iffe re n t p e rip h e ra ls, h o w to in te rfa ce
8080 program s to BASIC, h o w to access the disk system, a n d m uch m ore. A n d , it's easy to
le a rn , using o u r n e w c o d e -fra g m e n t a p p ro a ch w ith DDT. If y o u 're re a d y to a d va n ce
be yo n d sim p ly ru n n in g a p p lic a tio n program s, then this b o o k is fo r you!
• Learn 8080 A ssem bly Language P rogram m ing
• Find out h o w C P /M re a lly w orks
• G et started fast w ith our easy DDT C ode-F ragm ent a p proach
• D iscover h o w to use C P /M System Calls
• Access C P /M fro m BASIC
• Learn h o w to M o d ify BIOS