Commodore 128 Assembly Language Programming
Commodore 128 Assembly Language Programming
Assembly Language
Programming
Mark Andrews
Commodore 128®
Assembly Language
Programming
Commodore 128
Assembly Language
Programming
Mark Andrews
FIRST EDITION
FIRST PRINTING-1986
Trademark Acknowledgements
Preface xi
Introduction xiii
2 By the Numbers 19
Exploring the binary, hexadecimal, and decimal
number systems
What's in a Bit?
Prefixes $ and %
Binary Number System
Hexadecimal Number System
Comparing Binary and Hexadecimal Numbers
Converting from One System to Another
16-Bit Numbers in PEEK and POKE Commands
viii Contents
3 In the Chips 39
A peek inside the 8502 microprocessor
Improved Twice Over
All in the (6502) Family
What Every Computer Is Made Of
RAM and ROM
Inside a CPU
Bibliography 369
Index 371
Preface
Why is this book different from all other books on Commodore
128 assembly language? Let us count the ways.
1. There are other books about 6502 assembly language, and there
are other books about Commodore 128 machine language. But
this book combines everything you need to know about 6502
assembly language, Commodore 128 machine language, and
Commodore 128 assembly language. Many books that claim to
teach C-128 assembly language are actually about C-128 ma
chine language—and the difference between machine language
and assembly language is very important. Assembly language is
used by professional programmers to write commercial pro
grams for the Commodore 128. Machine language, though ade
quate for writing short routines to incorporate into BASIC
programs, is simply not the language to use if you want to write
high-performance Commodore 128 programs.
2. We'll not only cover the C-128 monitor (as do most books on C-
128 programming), but also learn how to use several of the
assembler/editor systems that professional programmers use to
write assembly language programs. The C-128 monitor is a use
ful programming utility, and this book covers it in detail. How
ever, the focus is primarily on the programming tools that
professional program designers use on the job.
3. This book contains a large collection of assembly language rou
tines—many graphics related—that are useful, as well as inter
esting and entertaining. By typing, assembling, and saving these
programs as you learn C-128 assembly language, you'll have a
library of useful (and perhaps essential) assembly language rou
tines that can be incorporated easily into your own BASIC and
assembly language programs.
You'll learn how to create your own character sets, and how
to use joysticks and mouse controllers in text and high-resolu
tion programs. You'll learn how to take advantage of the C-128's
advanced capabilities by using programming techniques such as
bank switching, interrupts, and self-modifying assembly lan
guage routines. You'll learn how to use the C-128's 80-column
screen to display both text and high-resolution graphics, and
how to write programs that use music and sound. And, as a
xii Preface
4. As you read this volume, you may notice that it's in English, not
computerese. It was written by a Commodore owner for other
Commodore owners—not by an electronics professor for engi
neering students and professional programmers (although many
of them could learn something from it, too). If you understand
BASIC—even a little BASIC—you'll be able to understand Com
modore 128 Assembly Language Programming.
5. Finally, the format, from start to finish, is tailored to make the
study of assembly language as painless as possible.
Introduction
Principles and
Techniques of
Assembly
Language
Programming
4 Principles and Techniques of Assembly Language Programming
Since you're reading this book, there's a good chance that you're
interested in learning Commodore 128 assembly language. And
there's no better time to start than right now. So, in listing 1-1, you'll
find a program that you can type, assemble, and execute immediately
using the computer's built-in machine language monitor. (If you don't
know how to use the C-128 monitor, you'll have to wait, but not for
long; before you're finished with this chapter, you'll be provided
with a brief but handy crash course on how to use the computer's
built-in monitor.)
SYS 3072
MONITOR
To turn off the monitor and return to BASIC, you can type:
Figure 1-1 PC SR AC XR YR SP
C-128 monitor's ;FBOOO 00 00 00 00 F8
startup display
three memory blocks are labeled block A, block B, and block C. The
screen map showing the configurations of these memory blocks is in
figure F-l (in appendix F). We will examine these memory blocks
more closely in chapter 10.
To help programmers access memory blocks A, B, and C easily
and conveniently, the computer is also equipped with 16 preset mem
ory configurations called banks. The use of the word bank is a little
misleading because the 16 memory banks in the C-128 are not 16
continuous blocks of memory. Instead, they are 16 different arrange
ments of memory segments chosen from memory block A, memory
block B, and memory block C.
The C-128's 16 banks of memory, numbered from 0 to 15 in
BASIC and from $0 to $F in hexadecimal (or hex), work something
like a menu in a Chinese restaurant; each is made up of a mixture of
whatever RAM and ROM it needs from memory block A, memory
block B, and memory block C. Of the 16 memory banks built into the
C-128, there are currently only four that are commonly used: banks 0
and 1, which are made up mostly of RAM from memory blocks A and
B, and banks 14 and 15, which are made up primarily of ROM from
memory block C. A chart showing the configurations of memory
banks 0, 1, 14, and 15 (or banks 0, 1, E and F in hexadecimal nota
tion) is in figure F-2 (in appendix F). A fuller description of this
memory map is presented in chapter 10.
Bank Switching
Each of the C-128's 16 memory banks has been designed for a spe
cific purpose; for example, a program may use one bank for calling
kernel routines, another bank for running BASIC routines, and still
another bank when certain ROM cartridges are plugged in. If you
like, you can switch from any bank to any other bank at any time
using a technique called bank switching.
Bank switching is used quite often in assembly language pro
gramming, and it is a particularly important part of C-128 program
ming because of the way the computer is designed. Although the
C-128 is built around an 8-bit microprocessor, its unusual banking
structure makes it capable of accessing much more memory than an
8-bit chip was designed to handle. Without bank switching, the C-128
would be just another 64K computer. With bank switching, it can
access more than 128K of memory, although it can do so only 64K at
a time.
For switching from bank to bank in BASIC programs, BASIC 7.0
—the version of Commodore BASIC that's built into the 128—has a
special BANK command. A number of other bank-switching tech
niques, some of them quite complicated, are also available to the
C-128 assembly language programmer. These techniques are ex
plored in detail in later chapters, particularly in chapter 10.
8 Principles and Techniques of Assembly Language Programming
type the 0 that precedes memory address OCOO. But, because it never
hurts to make everything in a program as clear as possible, bank 0 is
specified in the first line of the COLORME64.OBJ program.
When the bank number and the starting address of a program
have been typed in, the program itself can be typed and assembled.
In the COLORME64.OBJ program, the first group of executable code
is:
LDA #$0E
which means "load the accumulator with the number $0E" (or the
number 14 in decimal notation). So, when the COLORME64.OBJ
program is executed, the first thing that happens is that the number
14 ($0E in hex) is stored in the 8502 register called the accumulator.
As mentioned, the accumulator's main job is handling arithmetical
and logical functions. But the accumulator is also used quite often as
a temporary storage area for data being copied from one memory
address to another. And that is how the accumulator is used in the
COLORME64.OBJ program.
The dollar sign preceding the number 0E, as you may recall
from a few paragraphs back, shows that the number is written in
hexadecimal notation. And the # symbol that precedes the dollar sign
means that the number will be interpreted not as a memory address,
but as a literal number. If $0E was not preceded by the # symbol, the
accumulator would be loaded with the contents of memory address
$0E. Because the # symbol does appear, however, the accumulator is
loaded with the literal number $0E.
The second statement in the COLQRME64.OBJ program is:
STA $D020
In 8502 assembly language, this means "store the value of the accu
mulator into memory address $D020." So, the STA instruction stores
the value of the accumulator in memory address $D020. (The value
of the accumulator does not change when this procedure takes place,
so whatever is in the accumulator before the STA instruction is issued
will still be there after the instruction has been carried out.)
Now let's review what happens when the first two lines of the
COLORME64.OBJ program are executed. First, the statement LDA
#$0E loads the value $0E into the accumulator. Then, the statement
STA $D020 copies the value of the accumulator—which is now the
number $0E—into memory location $D020.
RTS Instruction
The last instruction in the COLORME64.OBJ program is RTS, which
means "return from subroutine." In 6502/8502 assembly language,
this instruction serves a double function. When RTS is used as the
last instruction in a subroutine, it works like the BASIC instruction
RETURN; it ends the subroutine and returns control of the program
being processed to the instruction following the one that called the
subroutine.
But when RTS is used as the last statement in an assembly
language program, it returns control of the C-128 to whatever system
was in operation before the program was executed—usually, the com
puter's BASIC interpreter. So, if BASIC is running when a machine
language program is called and if the program ends with an RTS
instruction, the RTS instruction ends the program and returns control
of the C-128 to the computer's built-in BASIC interpreter. Because
the COLORME64.OBJ program ends with an RTS instruction, this is
exactly what happens when the program is executed using the BASIC
command SYS 3072. After it changes the colors on your C-128's
screen, the program ends and returns control to BASIC.
—tells us not only that the assembly language instruction LDA #$0E
has been typed into the monitor, but also that its machine language
12 Principles and Techniques of Assembly Language Programming
After you type that line and press the Return key, your C-128 monitor
will move to the next line on the screen and display the value:
00C02
This tells you the value that the 8502 program counter will hold after
it executes the statement LDA #$0E; in other words, $0C02 is the
memory address into which the next instruction in the program will
be assembled. It's also the C-128 monitor's way of telling you that it
has assembled a line of source code and is ready for you to type
another line.
Now let's examine the rest of the COLORME64.OBJ program.
Listing 1-2 is the assembly language version of the program.
D 0C00
S "COLORME64.OBJ",8,0C00,0C0F
where program name is the name of the program being loaded, and dn
is the disk drive device number (usually 8). For example, after the
COLORME64.OBJ program is stored on a disk, it can be loaded into
memory using this command line:
L MCOLORME64.OBJM,8
SYS 3072
The C-128 can also carry out a number of other commands, all of
which are discussed in chapter 4.
Introducing C-128 Assembly Language 15
Low-Byte-First Rule
One difference between assembly language and machine language is
the order in which 2-byte addresses are written. In the second line of
the program in listing 1-3, for example, the assembly language state
ment STA $D020 is assembled into the machine language statement
8D 20 DO. The number $8D is the machine language equivalent of
the assembly language mnemonic STA. But two bytes that make up
the operand of the STA instruction are reversed when they are con
verted into machine language. This quirk arises from the fact that 2-
16 Principles and Techniques of Assembly Language Programming
What's in a Bit?
A bit can express only two values, and in the binary number system,
those two values are expressed as zeros and ones. When a bit is
turned off, or cleared, its value is said to be 0. And when a bit is
turned on, or set, its value is said to be 1.
Bits are extremely important in computer programming, and
here's why: A computer is really nothing but a vast collection of tiny
electronic switches, sometimes called gates, all connected in various
ways. Each of these switches, at its most fundamental level, works
much like a switch that controls a light. A gate, like a light switch, is
always in one of two states; it's either on or it's off. If it's on, electric
ity can flow through it; if it's off, an electrical impulse cannot get
through.
For the sake of convenience, the ones and zeros used to express
the values of bits are usually written in groups. A group of 4 bits is
called a nibble (or nybble), a group of 8 bits is called a byte, and a group
of 16 bits is called a word. The numbering system used to express the
states of bits in this fashion is the binary system.
As we saw in chapter 1, however, the binary system is not the
only number system used in writing assembly language programs.
Another system, called the hexadecimal system, is also used exten
sively in assembly language programming. We will be covering hex
adecimal numbers later in this chapter. There is also a third number
system that is often used in assembly language programming. But
fortunately, it's one you're probably already familiar with—the deci
mal system.
So now you know that three kinds of numbers are commonly
used in assembly language programming. To summarize, they are:
Prefixes $ and %
When a binary number appears in a 6502/8502 assembly language
program, the prefix % often appears before it. This symbol is used to
distinguish a binary number from a decimal or hexadecimal number.
When a hexadecimal number appears in a program, the $ prefix
indicates that it is a hexadecimal number. No special prefix is used
before a decimal number; so, if a number without a prefix appears in
a program, it is assumed to be a decimal number.
Figure 2-1 shows how prefixes are used to distinguish binary,
hexadecimal, and decimal numbers from each other in 6502/8502
assembly language programs.
Penguin Math
One of my favorite methods of explaining the concept of binary
numbers is with the help of something I call Penguin Math. Penguin
Math is the number system that penguins would probably use if
penguins could use numbers. To get an idea of how Penguin Math
works, just imagine that you are a penguin living on Penguin Island.
Now a penguin doesn't have five fingers on each hand. Instead, a
penguin has just two flippers. So, if you are a penguin and are looking
for something to count with, you don't have ten fingers to work with.
You're stuck with just two flippers. Instead of being able to count up
to 10 on your fingers, you're only able to count up to 2.
But suppose that you are an extremely smart penguin, a regular
Einstein among your peers on Penguin Island. Then you might be
able to figure out a way to count past 2 by using just two flippers.
Here's one way that could be arranged.
The numbers in figure 2-2 are binary numbers. And they clearly
show how a real smart bird can use two flippers to express four
values—the values 0 through 3. Obviously, that's a considerable im
provement over using your flippers to express only the numbers 1
and 2.
Discovering Feet
Now let's suppose that, while scratching your new number system in
the ice on Penguin Island, you happen to notice that you are standing
on two more flippers.
Voila—bigger numbers!
Now, by using your two bottom flippers along with your two top
flippers, you can count all the way up to 15, as shown in figure 2-3!
ical experiments, the two of you can now sit on the ice and count
even higher, using 8-bit Penguin Math, as illustrated in figure 2-4.
128 64 32 16 8 4 2 1
Place Values
= 255
11111111 = 65,535
Table 2-1
Decimal Number Hexadecimal Equivalent
Decimal-to-
Hexadecimal 1 1
2 2
Conversion Chart
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 A
11 B
12 C
13 D
14 E
15 F
16 10
Table 2-2
Decimal Number Hexadecimal Number Binary Number
Decimal,
Hexadecimal, and 1 1 00000001
2 2 00000010
Binary Numbers
3 3 00000011
Compared 4 4 00000100
5 5 00000101
6 6 00000110
7 7 00000111
8 8 00001000
9 9 00001001
10 A 00001010
11 B 00001011
12 C 00001100
13 D 00001101
14 E 00001110
15 F 00001111
16 10 00010000
Programmer's Calculators
There are some calculators that can perform decimal-to-hexadecimal
and hexadecimal-to-decimal conversions in a flash, and can even add,
subtract, multiply, and divide both decimal and hexadecimal num
bers. For example, Texas Instruments makes an extremely useful
decimal/hexadecimal calculator called the Programmer. Several other
companies also manufacture decimal/hexadecimal calculators, but
the TI Programmer is the most versatile and easy-to-use model that I
have found. Many assembly language program designers use the TI
Programmer, or some similar calculator, and wouldn't dream of try
ing to get along without it. If you get the chance to buy the TI
Programmer or some other good decimal/hexadecimal calculator, do
it! It won't cost much, and it will be well worth the expense.
HEX$(d)
DECf'/z")
PRINT HEX$(53280)
PRINT DEC(MD020M)
Binary-to-Decimal Conversion
Binary-to-decimal conversion might seem difficult at first, but it isn't
too tough after you get the hang of it. In a binary number, as we saw
earlier in this chapter, the rightmost bit always represents the deci
mal number 1—which, in higher mathematical terms, can be ex
pressed as 2 to the power of 0. The next bit to the left represents 2 to
the power of 1, the next represents 2 to the power of 2, and so on.
The bit positions in an 8-bit binary number are numbered 0 to 7,
starting from the rightmost digit. The rightmost bit—bit 0—represents
2 to the Oth power, or the number 1. And the leftmost bit—bit 7—is
equal to 2 to the 7th power, or 128. Figure 2-9 is a list of simple
equations that illustrate the meaning of each bit in an 8-bit binary
number.
The format used in figure 2-9 illustrates one easy way to convert
a binary number into a decimal number. Here's the method: Instead
of writing the binary number in its usual form, from left to right,
write it in a vertical column, with bit 0 at the top of the column and
bit 7 at the bottom. Next, multiply each bit in the binary number by
By the Numbers 31
the decimal number that it represents. Then add the results of these
multiplications. The total you get is the decimal value of the binary
number.
As an example, suppose you want to convert the binary number
00101001 into a decimal number. This is how to do it:
1 X 1
0 X 2 = 0
0 X 0
1 X 8 = 8
0 X 16 = 0
1 X 32 = 32
0 X 64 = 0
0 X 128 = 0
Total = 41
Decimal-to-Binary Conversion
Now we'll reverse direction and convert a decimal number to a
binary number. Here's how to do it: First, pick a decimal number-
any decimal number. Then divide it by 2. Next, write down both the
quotient and the remainder. Because we're dealing with a division by
2, the remainder is either 1 or 0. So what we end up writing is a
quotient, followed by a remainder of either 1 or 0. Next, take the
quotient, divide it by 2, and write it down. If there's a remainder (a 1
or a 0), jot that down too, underneath the first remainder.
When there are no more numbers left to divide, write down all
of the remainders, reading from the bottom to the top of the list.
What we have is a binary number—a number made up of ones and
zeros. That number is the binary equivalent of our original decimal
number.
This conversion technique is illustrated for the decimal number
117 as follows:
32 Principles and Techniques of Assembly Language Programming
Decimal-to-Hexadecimal Conversion
Decimal-to-hexadecimal conversion also looks rather difficult at first
glance, but it really isn't too difficult to master. First, divide the
decimal integer by 16. Then write down the remainder, like this:
C
1
c
F
Read these four numbers, starting from the bottom and reading
up, and you have the hexadecimal number FC1C, which is the num
ber we're looking for: the hex equivalent of decimal 64540!
This conversion process works with any decimal integer. But
there is an easier way to convert a decimal number to a hexadecimal
number: Let your computer do it for you, with a BASIC program!
(The program is listed later in this chapter.)
Table 2-3
Hexadecimal Number Binary Number
Hexadecimal-to-
Binary Conversion 0 0000
1 0001
Chart
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111
An Easier Way
Even though it isn't difficult to convert binary numbers to hex
adecimal numbers and vice versa, it is time consuming to do it by
34 Principles and Techniques of Assembly Language Programming
PEEK16.BAS 20 AL=32768:AH=32769
program 30 BANK 0
40 X=PEEK(AH)*256+PEEK(A L)
50 BANK 15
60 PRINT X
Figure 3-1 is a block diagram that shows the four major compo
nents of a computer system. It also shows that a computer's processor
unit is, in turn, made up of two parts: a CPU, or central processing unit,
42 Principles and Techniques of Assembly Language Programming
and main memory. Main memory is subdivided into two parts: ran
dom-access memory (RAM) and read-only memory (ROM). Let's pause
for a moment to examine these two types of memory. Then we'll
move on to the CPU, the main topic of this chapter.
Architecture of a
computer system
Inside a CPU
As you may recall from chapter 1—and as figure 3-2 illustrates—the
8502 chip (the CPU) does its work with the help of six internal regis
ters, or memory registers, that can be used to store and manipulate
data. In addition, the 8502 contains a very important component
called an arithmetic and logic unit, or ALU. The ALU, as its name
implies, can perform arithmetical and logical operations. We'll see
how the ALU works a little later in this chapter.
Your computer also contains a set of transmission lines called
buses. Buses are also appropriately named; their job is to move data
44 Principles and Techniques of Assembly Language Programming
Figure 3-2
Inside the 8502
8-Bit Data Bus
i i
i
ACC
N
Memory
-
Input/ PC SP P X Y
V\aLu/
\ll
Output B (RAM and
D ROM)
Z \7
c 1
i
ALU
One of the busiest components in the 8502 chip is the arithmetic
and logic unit, or ALU. Every time your computer performs a
calculation or a logical operation, the ALU is where the work is
accomplished.
The ALU can actually perform only two kinds of calcula
tions: addition operations and subtraction operations. Division and
multiplication problems can also be solved by the ALU, but only
in the form of sequences of addition and subtraction operations.
The ALU can also compare values. But as far as the 8502 chip is
concerned, the comparison of two numbers is also an arithmetical
operation. When the 8502 chip compares two values, it subtracts
one value from the other. Then, by checking the results of this
subtraction operation, it can determine whether the subtracted
value is more than, less than, or the same as the value from which
it was subtracted.
The 8502 chip's ALU has two inputs and one output. When two
numbers are to be added, subtracted, or compared, one number is
put in the ALU through one of its inputs, and the other number is put
in through the other input. The ALU then carries out the requested
calculation, and puts the answer on a data bus so that it can be
transported to another register.
46 Principles and Techniques of Assembly Language Programming
ALU Hopper
As figure 3-2 illustrates, the ALU is often depicted in diagrams as a V-
shaped hopper. The ALU has two inputs, which are traditionally
illustrated as the two arms of the the hopper, and one output, tradi
tionally represented as the bottom of the V.
Because the accumulator now holds the value 4 (the sum of 2 and 2),
the number 4 will be stored somewhere.
As you can see, the memory address that follows the STA in
struction is $ FA—the hexadecimal equivalent of the decimal number
250. So it appears that the number 4 will be stored in memory regis
ter $FA. Now take a close look at the hexadecimal number $FA in
line 3. Because there is no # sign in front of the number $FA, the
assembler does not interpret it as a literal number. Instead, $FA is
interpreted as a memory address, which is what a number has to be
in assembly language if it is not designated as a literal number and
carries no other identifying labels.
Incidentally, if you did want the assembler to interpret $FA as a
literal number, you would have to write it as #$FA. When # and $
both appear before a number, it is interpreted as a literal hexadecimal
number. If the third line of the program was STA #$FA, however, it
would result in a syntax error. That's because STA ("store the con
tents of the accumulator") is an instruction that has to be followed by
a value that can be interpreted as a memory address—not by a literal
number.
These four flags are used to keep track of the results of operations
being carried out by the other registers inside the 8502 processor.
Because the P register is an 8-bit register, it has four more bits
that could be used as flags, but only three of those flags are used.
Called condition flags, they are used to determine whether certain
conditions exist in a program. The P register's three condition flags
are:
Figure 3-3
7 6 5 4 3 2 1 0 Bit Positions
8502 processor
status register N V - B D 1 Z 1 C 1 Flags
The bits in the 8502 status register—like the bits in all 8-bit
registers—are customarily numbered from 0 to 7. By convention, the
rightmost bit in an 8-bit register is bit 0, and the leftmost bit is bit 7.
The positions of each bit in the 8502 status register can be seen in
figure 3-3.
A Closer Look
Now let's take a closer look at each bit, or flag, in the processor status
register.
As figures 3-4 and 3-5 illustrate, at the rate of one byte per digit,
it takes three times as many bytes to store the number 255 in BCD
notation as it does in binary notation. There are many applications in
which BCD numbers use even more memory. For example, when the
8502 performs floating-point arithmetic, extra bytes are usually re
quired to indicate how many digits there are in the number, whether
the number is positive or negative, and how many decimal places
there are in the number.
In floating-point arithmetic, which is often used in "number-
crunching" operations because of its high degree of accuracy, it could
take six or more binary numbers to express a three-digit decimal
number. Figure 3-6 shows how the number 2.55 might be expressed
as a 6-byte BCD number.
Figure 3-6 is only one illustration of how a number can be
expressed as a BCD number in floating-point applications. There are
many other methods for converting decimal numbers into BCD num
bers for use in floating-point operations.
In addition to using extra memory, BCD arithmetic is slower
than binary arithmetic. But because BCD numbers are based on 10,
like conventional decimal numbers, they are more accurate in arith
metical operations that use fractions and decimal values. So BCD
arithmetic is often used in programs when accuracy of calculations is
more important than speed or memory efficiency.
52 Principles and Techniques of Assembly Language Programming
Bit 4: Break (B) Flag—The break (B) flag is set automatically when
the assembly language instruction BRK is used to halt a program. It is
also set when certain error conditions stop a program. When a break
occurs in a program, the break flag is set and certain error-checking
operations take place. Because a BRK instruction sets the break flag,
program designers often use BRK instructions during the debugging
phase of writing programs. After the debugging of a program has
been completed, any BRK instructions placed in the program for use
during debugging are usually removed.
Other than the BRK instruction, there are no specific assembly
In the Chips 53
Bit 7: Negative (N) Flag—The negative (N) flag is set when the result
of an operation is negative and cleared when the result of an opera
tion is zero. The negative flag is often used in operations involving
signed numbers. In addition, the negative flag is often used to detect
whether a counter in a loop has decremented past zero, and it is
sometimes tested to see whether one number is less than another
number. The negative flag has other uses that are discussed in later
chapters. There are no instructions to set or clear the negative flag;
it's strictly a read-only bit.
Writing an
Assembly Language
Program
Using three
popular assemblers
and the C-128 monitor
56 Principles and Techniques of Assembly Language Programming
A Programmer's Toolkit
In this chapter, we will cover all of the methods of creating machine
language programs for the Commodore 128. To write and assemble
the programs in this chapter, we will use the following:
What's an Assembler?
Before we start examining the Merlin 128 assembler, let's get one
potentially confusing topic out of the way: As you may have con
cluded by now, the word assembler can have different meanings,
depending upon the context in which it is used. When programmers
speak of an assembler, they're sometimes talking about one part of a
software development package—the part that does the actual work of
converting assembly language into machine language. But the word
assembler can also refer to a complete assembly language program
ming package, such as the Commodore 64 Macro Assembler Devel
opment System or the Merlin 128 assembler/editor program. And
software packages like these usually include more than just an assem
bler. Other types of programs that are often contained in assembler
software packages include editors, monitors, loaders, and debugging
utilities.
• Executive module
• Editor module
• Assembler module
• Monitor module
• Symbol Table Generator module
When you use the Merlin 128 assembler, the modules that
you'll encounter most often are the Executive, Editor, Assembler, and
Monitor modules. You'll seldom have to worry about the Symbol
Table Generator module. Its job is to compile tables of constants and
variables, and it does its work automatically and quite transparently,
usually without any assistance from the programmer.
The Merlin 128 assembler/editor, like most commercial pro
grams for the C-128, boots automatically. So, to get the assembler up
and running, put the Merlin 128 disk in your disk drive and either
press the C-128's reset button or turn the system on. Then, after a
few moments of disk spinning, Merlin's master menu will appear on
your screen.
58 Principles and Techniques of Assembly Language Programming
Merlin's Menu
Figure 4-1 shows what the menu looks like when Merlin is in Execu
tive mode.
ADDNRS.S Program
Merlin offers many other editing features, including functions to
delete blocks of code, move blocks of code, and copy blocks of code
from one part of a program to another. Merlin's editing functions are
described in detail in the instruction manual that comes with the
assembler. But you don't need to know all of Merlin's editing func
tions to write an assembly language program. In fact, using only the
instructions we have just covered, you can write an assembly lan
guage program now. Start by simply typing an asterisk, as follows:
and pressing Return. The cursor moves to the next line, and the
number in Merlin's line counter window automatically advances to
2.
Now, without moving the cursor, type:
* ADDNRS.S
* ADDNRS.S
*
Listing 4-1 *
ADDNRS.S program * ADDNRS.S
•
ORG $1300
ADDNRS CLD
CLC
LDA #2
ADC #2
STA $0C00
RTS
END
Commodore key and the white left-arrow key (not the grey left-arrow
key that moves the cursor). Then Merlin will list your program,
complete with line numbers, on the computer screen.
Because the line numbers generated by Merlin always start with
1 and progress in increments of 1, they can—and do—change dynami
cally while you write a program. Therefore, they are sometimes re
ferred to as relative line numbers. And, although the current line
number always appears in the line counter window on the screen, the
only way that you can see the line numbers in a program is to use the
L (for "list") command, which lists the program on the screen.
When you have listed a program, Merlin displays another :
prompt, and you can put the assembler back into editor mode by
typing another A command. Alternatively, you can put Merlin back
into editor mode by typing the E command, followed by a line num
ber. When Merlin goes into editor mode in response to an E com
mand, the portion of the program that contains the desired line
appears on the screen, and the cursor is positioned at the beginning of
the line to be edited.
You can also return Merlin to editor mode by typing I (for
"insert") followed by the number of the line to be inserted. The I
command works like the E command, except it creates a blank line in
the program at the point where the new line is to be inserted.
When a program has been listed and the : prompt appears on
the screen, you can delete a line by typing D (for "delete"), followed
by the number of the line (or lines) you want to delete. Suppose you
want to delete lines 2 and 3 in the preceding listing. Type:
D2,3
after the : prompt. Then restore the lines you've deleted by using the
A command.
Here's an important point to remember about the Merlin 128
assembler. When a block of code is inserted into a program being
written on a Merlin assembler, or when a block is deleted, Merlin
automatically adjusts all subsequent line numbers to accommodate
the change. So, when you write a program using the Merlin 128,
remember that the line numbers in the program—particularly the line
numbers that follow material being inserted, deleted, or edited—can
always change without notice.
In addition to the A, I, and D commands, Merlin also has com
mands to copy lines, move lines, find and replace strings, and per
form many other useful functions. You can find full details on how to
use all of these functions in the Merlin 128 instruction manual.
We've examined the ADDNRS.S program so many times that
it's probably beginning to look familiar. The Merlin 128 version of
the program, like the version of the program presented in chapter 3,
adds the literal numbers 2 and 2 and then stores their sum in mem
ory. This all happens in lines 7, 8, and 9.
As familiar as all of this is, however, the Merlin version of the
62 Principles and Techniques of Assembly Language Programming
4 ORG $1300
5 ADDNRS CLD
6 CLC
7 LDA #2
8 ADC #2
9 STA $0C00
10 RTS
11 END
Not all assemblers handle line numbers in the same way. For
example, the Commodore Macro Assembler lets you assign your own
line numbers, and the TSDS assembler lets you decide whether you
want to assign line numbers or let your assembler do it for you. And
some assemblers, such as the ORCA/M assembler for the Apple II,
are equipped with full-screen editors that don't use line numbers at
all.
Label Field
Although labels have a field of their own, they are also optional in
6502/8502 assembly language programs. Some assemblers, such as
the miniassembler built into the Commodore 128 machine language
monitor, are not designed to handle labels—and that is a significant
shortcoming of the C-128 monitor. When labels are not used in an
assembly language program, the only way to access a routine or a
subroutine is to call it using the actual memory address at which it
begins. And memory addresses of routines and subroutines in a pro
gram tend to change quite often during the writing and editing of the
program.
tine and return control to the main program. (The instructions JSR
and JMP are discussed at greater length in later chapters.)
A label can be as short as one character and as long as the
assembler being used permits. Most assembly language program
mers write labels that contain three to six (or sometimes eight)
characters.
Op Code Field
An operation code (or op code) mnemonic is just a fancy name for an
assembly language instruction. There are 56 op code mnemonics in
the 6502/8502 instruction set, and they are the only ones that can be
used in Commodore 128 assembly language instructions.
In source code listings of assembly language programs, op code
mnemonics, such as CLC, CLD, LDA, ADC, STA, and RTS, are typed
in the op code field. When you write a program using the Merlin 128
assembler, each op code mnemonic must start at least two spaces
after a line number, or one space after a label. An op code mnemonic
placed in the wrong field will not be flagged as an error when you
type your program, but will be flagged as an error when your pro
gram is assembled.
The op code field in a source code listing is also used for direc
tives, or pseudo ops—words and symbols that are entered into a pro
gram like mnemonics but are not officially included in the 6502/8502
instruction set. The main difference between an op code and a
pseudo op is that an op code tells a program what to do, and a pseudo
op tells an assembler what to do. Pseudo ops, unlike 6502/8502 op
code mnemonics, vary from assembler to assembler. So, before you
use a pseudo op in an assembly language program, it's important to
find out whether the assembler you're using recognizes the pseudo
op.
Operand Field
The operand field in a Merlin 128 assembler program starts one
space (or one tab) after the mnemonic field. Some mnemonics
require operands, while others don't. Instructions we have en
countered so far that do not require operands include CLC, CLD,
and RTS. Instructions that do require operands include LDA, STA,
and ADC. The use of operands is covered in more detail in chapter
6, which focuses on the addressing modes used in 6502/8502 as
sembly language.
Writing an Assembly Language Program 65
Comment Field
Comments in assembly language programs are like remarks in BASIC
programs; they don't affect the execution of a program in any way,
but are often extremely useful because they can help explain how
each step in a program works and are thus an important part of the
documentation of a program.
There are two ways to include a comment in source code list
ings written on the Merlin 128 assembler. One method is to precede
the comment with an asterisk and put the comment in the label field
of a listing. The other method is to precede the comment with a
semicolon and put it in the comments field, which follows the oper
and field. This method of including comments is used in many of the
programs in this volume.
Line 6: CLC
Line 6 contains the CLC ("clear carry") statement. The status regis
ter's carry flag is affected by so many kinds of operations that it's
considered good programming practice to clear it before every addi-
Writing on Assembly Language Program 67
Line 7: LDA #2
Line 7, LDA #2, is a very straightforward instruction, and we already
know what it means ("load the accumulator with the number 2").
The first step in an addition operation is always loading the accumu
lator with one of the numbers that is to be added. The # sign before
the number 2 means that it's a literal number, not an address. If the
instruction was LDA 2, then the accumulator would be loaded with
the contents of memory address 0002, not the number 2.
Line 8: ADC #2
ADC #2, in line 8, is also a straightforward instruction. It means that
the literal number 2 is to be added to the number that's in the
accumulator (in this case, another 2). As mentioned, there is no 6510
assembly language instruction that means "add without carry." So
the only way that an addition operation can be performed without a
carry is to clear the status register's carry flag and then perform an
"add with carry" operation.
PRTR 4
DOS Wedge
In addition to all of the programs mentioned, the Commodore 64
assembler/editor disk also contains two useful programs called DOS
WEDGE64 and BOOT ALL. If you do much programming using a
Commodore 64, you probably know what a DOS wedge is; it's a
Writing on Assembly Language Program 71
utility that can come in handy when you're writing C-64 programs
because it makes the C-64's disk operating system a little easier to
use. So a DOS wedge also comes with the C-64 Macro Assembler
System. Both the DOS wedge and the BOOT ALL program are dis
cussed in more detail later in this chapter.
ADDNRS.SRC Program
First, though, here's a listing of the ADDNRS program—the same
program presented in the first section of this chapter—written using
the Commodore 64 assembler. To distinguish this latest version of the
program from the version produced using Merlin, we'll call this new
version ADDNRS.SRC rather than ADDNRS.S. Shortly, you'll get an
opportunity to type, assemble, and run the ADDNRS.SRC program. A
listing of the program appears in listing 4-4.
Listing 4-4 10 ■
ADDNRS.SRC 20 ;ADDNRS.SRC
program 30 i ■
40 *=$1300
50 ■
60 ADDNRS CLD
70 CLC
80 LDA #2
90 ADC #2
100 STA $0C00
110 RTS
120 .END
Label Field
Labels always occupy the first official field in Commodore 64 assem
bler programs, as they do in programs written with the Merlin 128
assembler. Exactly one space—not two—must be left between a line
number and any label that follows. If you start a label two or more
spaces after a line number—or if you use the tab key to get to the
label field—you may clobber your program.
•=$1300
Writing an Assembly Language Program 73
Comment Field
There are also differences in the way in which you write comments
using the Commodore and Merlin assemblers. In programs written
with the Commodore assembler, a comment that begins in field 2 is
preceded by a semicolon rather than an asterisk. A comment pre
ceded by a semicolon can also appear in the section after the instruc
tion fields (op code and operand fields). If you use the comment field
at the end of a line and don't have room for the entire comment, you
can continue your comment on the next line by simply typing a
space, a semicolon, and the rest of your remark.
A line-by-line explanation of the ADDNRS program was pro
vided earlier in this chapter, in the section on the Merlin 128 assem
bler. If you skipped that section because you're using the
Commodore 64 assembler, it would be a good idea to back up and
read the line-by-line program analysis now because the same expla
nations apply to the version of the program written on the Commo
dore 64 assembler. After you do that, you'll be ready to write and run
the ADDNRS.SRC program.
the line %:EDITOR64. Then you can put your editor program into
operation with the command SYS49152.
If that all sounds too complicated to remember, you may be
pleased to learn that there's a much simpler way to load both the
DOS wedge program and the Commodore 64 editor program. Just
insert the assembler/editor disk into the drive and type:
When you've typed that line, press Return, wait for the READY
prompt, and then type:
RUN
Listing 4-6 10
■
ADDNRS.SRC 20 ; ADDNRS.SR
program 30 ■
40 *=$1300
50 t ■
60 ADDNRS CLD
70 CLC
80 LDA #2
90 ADC #2
100 STA $0C00
110 RTS
120 .END
Listing 4-7 10 ■
program 40 *=$1300
50 9 ■
60 ADDNRS CLD
70 CLC
80 LDA #2
90 ADC #2
100 STA $0C00
110 RTS
120 .END
76 Principles and Techniques of Assembly Language Programming
OPEN 1,4,4
The first number can be any value between 1 and 255 and the
second value is the device number for a printer attached to a Commo
dore. If your printer is not device number 4, you will have to use a
different device number. After your printer's device channel is open,
you can type:
CMD 1
LIST
CLOSE 1
so that the screen will become your primary output device again.
PUT "ADDNRS.SRC11
The top red light on your disk drive should now go on, and the disk
you're storing the program on should start to spin. When your disk
drive's "busy" light goes off, the source code should be safely re
corded on a disk under the file name:
ADDNRS.SRC
Are you sure, though, that your program has been stored safely?
To find out, you can type:
NEW
Writing an Assembly Language Program 77
Then type:
GET "ADDNRS.SRC"
Now type:
LIST
If you're using the DOS wedge program, it's easy to load the
assembler that's on the C-64 assembler/editor disk. Remove the user
disk from the disk drive, insert the assembler/editor master disk, and
type:
/ASSEMBLER64
(If you're not using the DOS wedge, you'll have to type LOAD
"ASSEMBLER64",8-and if the assembler fails to load, you may
never know why!)
When you've loaded the assembler, type:
RUN
Now remove the assembler/editor disk from the disk drive, replace it
with your own program disk, and type the file name:
ADDNRS.ASM
78 Principles and Techniques of Assembly Language Programming
The assembler will assign that name to the object file it will soon be
creating.
Next, you're asked whether you want a hardcopy (printout) of
the assembly code listing. If you do, press Return. If you don't, type
N.
Your assembler/editor now asks if you want another kind of file
called a cross-reference file (so you can refer in other programs to any
labels, constants, or other identifiers you've created in this one). You
can't create an object code file and a cross-reference file during the
same pass through the Commodore 64 assembler, and you'll have no
need for such a file at this point in your study of assembly language.
So press Return.
Finally, you're asked to give the name of the source code pro
gram you want assembled. In response to this prompt, type:
ADDNRS.SRC
The C-64 assembler then assembles the program and provides you
with a listing—either on the screen or on paper, depending on what
you requested—that looks like the one in listing 4-8.
0001
Assembled listing LINE# LOC CODE LINE
of ADDNRS.SRC
program 00001 0000 ■
ERRORS = 00000
SYMBOL TABLE
SYMBOL VALUE
ADDNRS 1300
END OF ASSEMBLY
Writing an Assembly Language Program 79
TSDS Assembler
The TSDS (Total Software Development System) assembler com
bines some of the features in the Merlin 128 assembler with many
of the features in the Commodore 64 Macro Assembler System.
The TSDS assembler is not a menu-driven system; instead, it
works much like the BASIC editor built into the C-128. When the
TSDS system is up and running, you don't have to choose operat
ing modes from a menu; simply type in a command line, and the
assembler executes the command immediately. And, with the
TSDS system, you can mix DOS commands and assembler com
mands quite freely; when the TSDS assembler is running, you can
use DOS commands such as DLOAD, DSAVE, BLOAD, BSAVE,
SCRATCH, and NEW, and they will be executed just as they
would be if you were using the C-128's built-in BASIC 7.0 inter
preter.
80 Principles and Techniques of Assembly Language Programming
Listing 4-9 10 •
ADDNRS.SRC 20 ; ADDNRS.SRC
program, TSDS 30 ■
version 40 *=$1300
50 ■
60 ADDNRS CLD
70 CLC
80 LDA #2
90 ADC #2
100 STA $0C00
11 0 RTS
Line Numbers
The NoSync TSDS assembler, like the Merlin 128 assembler, loads
automatically when booted. It allows you to type assembly language
programs in much the same way that you type BASIC programs. The
TSDS assembler does not generate relative line numbers, as the Mer
lin 128 assembler does. Instead, it allows you to write your own line
numbers—starting with any number, ending with any higher num
ber, and progressing in any increments you desire.
Another important feature of the TSDS assembler is that it uses
a full-screen editor. When you create a program with TSDS, you can
use the C-128's arrow keys to move the cursor anywhere on the
screen—and, by placing the cursor over lines that have already been
typed, you can make corrections without retyping the lines.
If you have trouble being neat and tidy when you put spaces in
programs, you may be happy to hear that TSDS is not finicky about
spacing. In fact, you can put labels and mnemonics underneath each
other in a program, without any indentations, although it may make
your programs difficult to read. TSDS will sort out the mess when it
assembles your program.
TSDS, like all assemblers, uses its own set of pseudo op codes,
and they are different from the op codes recognized by both Merlin
and the C-64 Macro Assembler. TSDS recognizes an asterisk followed
by an equal sign (* = ) as a directive to set its program counter, and
uses the .END directive to end programs. The .BYTE directive used
by the C-64 Macro Assember (which is the same as the DBF or DB
directive used by Merlin) becomes .BYT in the TSDS assembler's set
of pseudo op commands. You can store 16-bit words in memory with
the .WOR ("word") and .DBY ("double byte") directives.
When you type a program using TSDS, you can delete blocks of
lines with the command DELETE, just as you can in BASIC 7.0
programs. But there's no easy way to move blocks of code from one
Writing an Assembly Language Program 81
ASM TO "ADDNRS.OBJ"
PUT ADDNRS.SRC
PUT "ADDNRS.SRC11
• LIST
C-128 Monitor
We wrote a short program using the Commodore 128 monitor in
chapter 1, so there's no need to repeat the information presented in
that chapter. But there are a few additional comments that could be
said about the C-128 monitor, so I'll say them before we move to
chapter 5.
As you may recall from chapter 1, you can activate the C-128
monitor by holding down the shift key while you start your com
puter, by pressing the reset button, or by typing the command MONI
TOR while BASIC is running. The screen that is displayed when the
monitor is activated is illustrated in figure 4-2.
Figure 4-2 PC SR AC XR YR SP
C-128 monitor's ; FBOOO 00 00 00 00 F8
opening display
The top line of the display in figure 4-2 is a list of the 8502
processor's six internal registers: the program counter, the processor
status register, the accumulator, the X register, the Y register, and the
stack pointer. The functions of all of these registers are described in
chapter 3. The second line of the display shows, from left to right: the
memory bank currently being accessed (in this case, bank F); the
current contents of the 8502 program counter (in this case, $B000);
and the current states of the 8502's other five registers.
01300 LDA #2
When a source code listing is typed using the C-128 monitor, the
monitor assembles each line as soon as the Return key is pressed, and
stores the line in memory. As each line of a program is assembled
Writing an Assembly Language Program 83
S "ADDNRS.OBJ",8,1300,130B
L "ADDNRS.0BJ"f8
84 Principles and Techniques of Assembly Language Programming
Limitations
The C-128 monitor is a handy programming utility, but its built-in
miniassembler has many limitations. For example, as we've men
tioned, it does not recognize labels and cannot be used to load or save
source code programs. That's why it's called a miniassembler.
Despite its limitations, however, the C-128 monitor can still
perform many useful functions. For example, it can be used to load
and execute machine language programs, as we'll see in the next
chapter. Before it can do that, though, the program to load and exe
cute must be stored on a disk. So, if you haven't yet assembled the
ADDNRS program and stored it on a disk, this might be a good time
to do so. Then you'll have it handy for the next chapter.
Running an
Assembly Language
Program
From a BASIC program,
or on its own
86 Principles and Techniques of Assembly Language Programming
• from DOS
• from BASIC
• from the C-128 machine language monitor
PC SR AC XR YR SP
; FBOOO 00 00 00 00 F8
L "ADDNRS.0BJ",8
D 1300 130A
segment of machine code back into source code. So, when you type
the preceding line, the Commodore 128 should respond with a disas
sembled (source code) listing of the ADDNRS program.
If you now have your monitor up and running, you can easily
run the ADDNRS program, too. On the next line on the screen, type:
G 1300
and press Return. The computer will respond with another READY
prompt—which means that something has definitely happened be
cause you've now exited the monitor and are back in BASIC!
Why is that? It's because the ADDNRS program ends with an
RTS instruction. When the C-128 monitor encounters an RTS instruc
tion without any address to return to, it returns control to BASIC.
There is, by the way, an assembly language instruction that can
prevent the Commodore monitor from returning to BASIC after it
finishes running a program using the G command. The instruction
that can keep this from happening is the mnemonic BRK (for
"break"). Programmers often use the BRK mnemonic when they're
debugging programs. By putting a BRK instruction at the end of an
assembly language routine, you can debug your program without
worrying about the assembler jumping back into BASIC every time it
comes to the end of the routine. When you've finished debugging a
program, though, remove any BRK instructions that have been used
for debugging because they can crash the program when the program
is run outside a monitor environment.
But that's not our problem now. We want to get our assembler
back into monitor mode. Type MONITOR—the same BASIC 7.0 com
mand that you used to activate the monitor in the first place—and the
assembler returns to monitor mode.
When your monitor's opening screen display reappears, you can
check to see whether the monitor has executed the ADDNRS pro
gram successfully. Type:
M 0C00 0C07
Your monitor should respond with a line that looks like figure 5-1.
figure 5-1 is 04, we can see that we've succeeded; the program has
added 2 and 2, and has stored the sum in memory address $0C00!
Listing 5-1 1 *
HITEST.S program 2 * HITEST.S
3 *
4 ORG $1300
5 *
6 LDA #72
7 JSR $FFD2
8 LDA #73
9 JSR $FFD2
10 RTS
How It Works
HITEST.S is a short program with a big secret; it's only five lines
long, yet it can print a message on your C-128's screen—a job that
would ordinarily require many more lines of code. The program can
do such a big job with so few instructions because it uses the C-128
kernel: a collection of useful machine language subroutines that can
be incorporated into user-written programs quickly and easily.
The C-128 kernel resides in a segment of ROM that extends
from memory address $E000 through memory address $FFFF. After
you know how to use it, you can perform many kinds of input/output
operations by simply calling a prewritten subroutine, rather than
writing the subroutine yourself. The kernel contains routines that can
read a character typed in at the keyboard, print a character on the
screen, switch to C-64 mode or 80-column mode, and perform many
kinds of file operations.
The C-128 kernel is guaranteed to be compatible with future
versions of the computer, so if you write programs using the kernel,
they won't become obsolete when models change. In fact, the Com
modore 64 also has a kernel, and the C-128 kernel is downwardly
compatible with the kernel built into the C-64. The C-128 kernel
Running an Assembly Language Program 89
contains more subroutines than the C-64 kernel, but the C-64 kernel
is a totally compatible subset of the kernel built into the C-128.
Most of the subroutines built into the C-128 kernel are very easy
to access. To use a kernel subroutine, all you usually have to do is
place certain values in one or more 8502 registers. Then you can use
the assembly language instruction JSR to jump to the address of the
desired subroutine. Another important feature of the kernel is that it
can save you memory because it eliminates the necessity of duplicat
ing routines that are already built into ROM.
The subroutine that is called by the HITEST.S program has a
call address of $FFD2 and is titled BSOUT. The same subroutine was
included in the Commodore 64 kernel, but in its C-64 version it was
called CHROUT. To use the BSOUT subroutine, place an ASCII value
in the 8502 accumulator and then do a JSR to memory address
$FFD2. The character that equates to the ASCII code in the accumu
lator will then be printed on the screen.
A complete list of the C-128 kernel subroutines can be found in
the Commodore 128 Programmer's Reference Guide and in other C-128
reference manuals. A number of kernel routines are used in other
programs in this book.
BLOAD "HITEST.O11
SYS 4864
SYS 4864#42,f9
Listing 5-3 1 *
COLORME2.S 2 * COLORME2.S
program 3 *
4 ORG $1300
5 *
6 STA $D020
7 STX $D021
8 STY $F1
9 RTS
SYS DEC(M1300M),14,6,14
RREG Instruction
BASIC 7.0 also has a handy instruction, RREG, that can can read the
8502 chip's A, X, Y, and S registers. When a machine language
program is executed from BASIC, the RREG instruction can tell you
what values these four registers hold at the conclusion of the pro
gram.
USR(X) Function
SYS is not the only reserved BASIC 7.0 word that can be used to
execute a machine language program. BASIC also has a function,
USR(X), that can call a machine language program or a machine
language routine.
Before the USR(X) function can call a machine language pro
gram, the program must be loaded into memory. Then the starting
address of the machine language program must be stored, low byte
first, in memory addresses $1219 and $121A (4633 and 4634 in deci
mal notation). Then the USR(X) function can be invoked using one of
the following formats:
Y=USR(X)
y=USR(?i)
Y=USR(X)
Y=USR(?i)
the n value can be passed to the machine language program, and the
program can then perform any desired function using that value.
When the machine language program ends and control returns to
BASIC, the machine language program can pass the result of its
calculations back to BASIC, as the value of the Y variable.
When the USR(X) function is executed, the value inside the
parentheses is stored in an area of memory called floating-point accu
mulator 1, or FAC1. Actually, there are two floating-point accumula
tors in the C-128; one is called FAC1 and the other is called FAC2.
When BASIC performs an arithmetical problem, it stores the values
that it is working with in FAC1 and FAC2. As their names imply,
FAC1 and FAC2 store numbers in floating-point fashion, in a format
similar to the one that was described in chapter 2.
At this point, the use of the USR(X) function gets a little compli
cated. Because floating-point numbers are not customarily used in
user-written programs, it is often necessary to convert the number
stored in FAC1 into a binary number before a desired USR(X) func
tion can take place. Fortunately, the C-128 has several built-in sub
routines that can convert floating-point numbers to binary integers,
and vice versa. One such subroutine, called GETADR, can be called
by doing a JSR to memory address $AF0C.
When GETADR is called, it expects a floating-point value to be
stored in FAC1. GETADR converts that value into a binary integer,
and stores the binary integer, low byte first, in memory addresses $16
and $17 (or 22 and 23 in decimal notation). The machine language
program can then perform any desired operation on the value stored
in $16 and $17, using ordinary binary arithmetic.
When the machine language program has finished its calcula
tions, it can convert the result of its operations into a floating-point
number before returning control to BASIC, thus ensuring that the
result of the calculation is passed back to BASIC in a form that BASIC
"understands."
An easy way to convert a binary value into a floating-point
value is to use another subroutine, called GIVAYF, which can be
called by doing a JSR to memory address $AF03. When GIVAYF is
called, it expects a 16-bit binary value to be stored in the accumulator
and the Y register, with the low byte in the Y register and the high
byte in the accumulator. GIVAYF converts these two values into a
floating-point number, and stores the number in FAC1. So, when the
USR(X) function is used to pass a value to a machine language pro
gram and then get a value back, both the number passed to the
machine language program and the number returned to BASIC can be
found in FAC1.
Listing 5-5, a program titled USRDEMO.SRC, shows how the
USR(X) function can pass a value to a machine language program,
and how the machine language program can perform an operation on
the value and pass the result of the operation back to BASIC.
94 Principles and Techniques of Assembly Language Programming
This chapter is much shorter than chapter 4, but that isn't sur
prising because it's much easier to run an assembly language pro
gram than it is to write one. And that's okay because a short chapter
means we can move on to the next chapter that much sooner. This
brings us to chapter 6, which is all about the addressing modes used
in 6502/8502 assembly language.
6
Addressing the
Commodore 128
Addressing modes
of the 8502
96 Principles and Techniques of Assembly Language Programming
Table 6-1
Column 1 Column 2 Column 3 Column 4
Addressing Modes Sample Assembly
of ADC Mnemonic Language Machine Code Number of
Addressing Mode Statement Equivalent Bytes
Table 6-2
... . . . , Addressing Mode Format
Addressing Modes &
Of 8502 Implicit (Implied) RTS
Accumulator ASL A (or ASL)
Immediate LDA #2
Absolute LDA $0C00
Zero Page STA $FA
Relative BCC LABEL
Absolute Indexed,X LDA $0C00,X
Absolute Indexed,Y LDA $0C00,Y
Zero Page,X LDA $FA,X
Zero Page,Y STX $FA,Y
Indexed Indirect LDA ($FA,X)
Indirect Indexed LDA ($FA),Y
Indirect JMP
ADDNRS.SRC Revisited
Listing 6-1 uses ADDNRS.SRC, an 8-bit addition routine that you may
recall from chapter 1, to show how some of the 8502' s addressing
modes work. The program was typed using a TSDS assembler, as
were the rest of the programming examples in this chapter. (If you
have a Merlin or Commodore assembler, or some other assem
bler/editor system, you can alter the program to meet your system's
demands without too many problems by now because the important
differences in the formats used by these assemblers were described
in chapters 4 and 5).
Immediate Addressing
When immediate addressing is used in an assembly language instruc
tion, the operand that follows the op code mnemonic is a literal
number—not the address of a memory location. Therefore, in a state
ment that uses immediate addressing, a # symbol (the symbol for a
literal number) always appears before the operand.
When an immediate address is used in an assembly language
statement, the assembler does not have to PEEK into a memory
location to find a value. Instead, the value itself is placed directly into
the accumulator. Then, the operation the statement calls for can be
immediately performed. Instructions that can be used in immediate
address mode are ADC, AND, CMP, CPX, CPY, EOR, LDA, LDX,
LDY, ORA, and SBC.
Accumulator Addressing
Accumulator addressing mode is used to perform an operation on
a value stored in the 6502/8502 processor's accumulator. When
accumulator addressing mode is used, some assemblers require
the letter A as an operand; other assemblers, including both the
Merlin 128 and the TSDS assembler, do not require the A operand.
One example of a statement that uses accumulator addressing
mode is ASL, or ASL A. This statement rotates each bit in the
accumulator one position to the left, with the leftmost bit (bit 7)
dropping into the carry bit of the processor status (P) register.
Other instructions that can be used in accumulator addressing
mode are LSR, ROL, and ROR.
Absolute Addressing
Absolute addressing is very similar to zero page addressing. In a
statement that uses absolute addressing, the operand is a memory
location, not a literal number. The operation called for in an absolute
address statement is always performed on the value stored in the
specified memory location, not on the operand itself.
The difference between an absolute address and a zero page
address is that an absolute address statement doesn't have to be on
page zero; it can be anywhere in free RAM. So an absolute address
statement requires a 2-byte operand—not a 1-byte operand, which is
all that a zero page address requires.
Listing 6-2 shows what the ADDNRS.S program looks like when
we use absolute addressing instead of zero page addressing.
100 Principles and Techniques cf Assembly Language Programming
The only new feature in listing 6-2 appears in line 1100. The
operand in that line is now a 2-byte operand, and that change makes
the program one byte longer. But now the address in line 1100
doesn't have to be on page zero; it can be the address of any free byte
in RAM.
Mnemonics that can be used in absolute addressing mode are
ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, EOR, INC, JMP, JSR,
LDA, LDX, LDY, LSR, ORA, ROL, ROR, SBC, STA, STX, and STY.
Relative Addressing
Relative addressing mode is used for a technique called conditional
branching—a method for instructing a program to jump to a given
routine under specific conditions. There are eight conditional branch
ing instructions, or relative address mnemonics, in 6502/8502 assem
bly language. All eight begin with B, which stands for "branch to."
Examples of conditional branching instructions that use relative ad
dressing are: BCC ("branch to a specified address if the carry flag is
clear"), BCS ("branch to a specified address if the carry flag is set"),
BEQ ("branch to a specified address if the result of an operation is
equal to zero"), and BNE ("branch to a specified address if the result
of an operation is not equal to zero"). All eight conditional branching
instructions are described in chapter 7, which is devoted to the topic
of looping and branching.
The eight conditional branching instructions are often used with
three other instructions called comparison instructions. Typically, a
comparison instruction is used to compare two values with each
other, and the conditional branch instruction is then used to deter
mine what to do if the comparison turns out in a certain way.
The three comparison instructions are: CMP ("compare the
number in the accumulator with..."), CPX ("compare the value in
the X register with..."), and CPY ("compare the value in the Y
register with..."). Conditional branching instructions can also follow
arithmetical operations, logical operations, and various kinds of test
ing of bits and bytes.
Addressing the C-128 101
1030 '*=$1300
1040 ■
LDA $0C00,X
LDA $0C00,Y
1140 DATMOV
1150 ■
1160 'ldx #0
1170 LOOP LDA TEXT,X
1180 STA TXTBUF,X
1190 CMP #EOL
1200 BEQ FINI
1210 INX
1220 JMP LOOP
1230 FINI RTS
1240 .END
When the program begins, we know that the string ends with an
end-of-line (EOL) character, the ASCII character $0D, which is gener
ated by the Return key on the C-128 keyboard.
As the program proceeds through the string, it tests each charac
ter to see whether it is a carriage return. If the character is not a
carriage return, the program moves to the next character. If the
Addressing the C-128 103
Zero Page,YAddressing
Zero page,Y addressing works like zero page,X addressing, but it can
be used with only two mnemonics: LDX and STX. If we didn't have
zero page,Y addressing mode, we couldn't use absolute indexed ad
dressing with the instructions LDX and STX—and that's the only
reason why we have this addressing mode.
Indirect Addressing
There are two subcategories of indexed addressing: indexed indirect
addressing and indirect indexed addressing. Both indexed indirect
addressing and indirect indexed addressing are used primarily to look
up data stored in tables.
If you think the names of these two addressing modes are con
fusing, you're not the first one with that complaint. I never could
keep them sorted until I came up with a little memory trick to help
eliminate the confusion. Here's the trick: Indexed indirect address
ing, which has an X in the first word of its name, uses the 6502/8502
chip's X register. Indirect indexed addressing, which doesn't have an
X in the first word of its name, uses the 6502/8502's Y register. Now
we'll look at each of the Commodore's two indirect addressing modes
—beginning with indexed indirect addressing.
$B0 = #$00
$B1 = #$80
X = #$00
Now let's suppose you are running a program that contains the
indexed indirect instruction LDA ($B0,X). If all of these conditions
exist when the computer encounters the instruction LDA ($B0,X), the
computer adds the contents of the X register (a 0) to the number $B0.
The sum of $B0 and 0 is $B0. So the computer goes to memory
address $B0 and $B1. It finds the number $00 in memory address
$B0, and the number $80 in $B1.
Because 6502/8502-based computers store 16-bit numbers in re
verse order (low byte first) the computer interprets the number found
in $B0 and $B1 as $8000. It loads the accumulator with the number
$8000, the 16-bit value stored in $B0 and $B1.
Now let's imagine that when the computer encounters the state
ment LDA ($B0,X), its 6502/8502's X register holds the number 04,
instead of the number 00. Here is a chart illustrating these values,
plus a few more values that we'll be using shortly:
$B0 = #$00
$B1 = #$80
$B2 = #$0D
$B3 = #$FF
$B4 = #$FC
$B5 = #$1C
X = #$04
$B0 = #$00
$B1 = #$50
Y = #$04
Figure 6-1
How the stack "Bottom" Stack
of Stack Addresses
pointer works
Stack Pointer
$FF $01FF
$01FE
$01FD
$01FC
again. Therefore, the stack pointer always points to the address of the
next value that will be stored on the stack.
Stack Operations
Let us suppose, now, that several numbers are stored on the stack.
Let us also suppose that the time has come to retrieve one of those
values from the stack. What happens?
You can probably guess the answer. When we retrieve a num
ber stored on the stack, the value of the stack pointer is incremented
by one. This effectively removes one value from the stack because it
means that the next value stored on the stack has the same position
on the stack as the one that was removed. That's a little tricky to
comprehend, given the upside-down nature of the stack. Figure 6-1
might help you understand how this works. This illustration shows
an empty stack, with the stack pointer pointing to the first available
address on the stack: $01FF.
Now let's place a number (whose value is arbitrary) on the
stack. This kind of operation is illustrated in figure 6-2. In this figure,
the value of the stack pointer has been decremented, and the number
we have placed on the stack is now stored at the highest address in
the stack, memory register $01FF.
Figure 6-3 shows what happens if we place another number
(also an arbitrary value) on the stack. In figure 6-3, the stack pointer
is decremented again, and a second number is now on the stack.
Figure 6-4 shows what happens when we "remove" one num
ber from the stack. In figure 6-4, stack address $01FE still holds the
value $B0, but the value of the stack pointer has been incremented
and now points to memory address $01FE. So the next number
108 Principles and Techniques of Assembly Language Programming
Figure 6-2
Placing a number "Bottom" Stack
on the stack of Stack Addresses
$2E $01FF
Stack Pointer
$FE ► $01FE
$01FD
$01FC
number on the
stack
$2E $01FF
$B0 $01FE
Stack Pointer
$FD $01FD
$01FC
Addressing the C-128 109
Stack Pointer
$01FD
$01FC
$17 $01FE
Stack Pointer
$FD $01FD
$01FC
Listing 6-5 is an example of a routine that makes use of the stack. You
may recognize it as a variation on the 8-bit addition program that
we've been using throughout this book. The program in listing 6-5 is
divided into two parts. In routine 1, we'll put two 8-bit numbers on
the stack. In routine 2, we'll take them off the stack and add them.
Routine 1 should be typed first. However, before the program is
assembled and executed, routine 2 should be appended to routine 1.
1030 *=$1300
1040 ■
1 030 *=$1300
1 040 ■
1 041 ; ROUTINE
1 042 ■
1 130 ; ROUTINE
1 150
1 160 CLD
Addressing the C-128 111
An Important Warning
But beware: It can very dangerous for beginning programmers to play
with the stack. When you use the stack in an assembly language
routine, it's extremely important to leave the stack exactly as you
found it when the routine ends. If you've placed a value on the stack
during a routine, it must be removed from the stack before the rou
tine ends and normal processing resumes. Otherwise, there might be
"garbage" on the stack when the next routine is called, and that can
result in program crashes, memory wipeouts, and various other pro
gramming disasters. Remember: Mismanagement of the hardware
stack is extremely hazardous to the health of assembly language
programs! So, if you take care to manage the stack properly, in other
words, if you make sure to clear the stack after each use, it can be a
very powerful programming tool.
Mnemonics that make use of the stack are PHA ("push the
contents of the accumulator onto the stack"), PLA ("pull the top
value off the stack and store it in the accumulator"), PHP ("push the
contents of the P register onto the stack"), and PLP ("pull the top
value off the stack and store it in the P register").
The PHP and PLP operations are often included in assembly
language subroutines so that the contents of the P register won't be
wiped out during subroutines. When you jump to a subroutine that
may change the status of the P register, it's always a good idea to start
the subroutine by pushing the contents of the P register onto the
stack. Then, just before the subroutine ends, you can restore the P
register's previous state with a PHP instruction. In that way, the P
register's contents won't be destroyed during the subroutine.
7
For a Loop
Looping and branching
in C-128 assembly language
114 Principles and Techniques of Assembly Language Programming
Now we're going to start having some real fun with Commodore 128
assembly language. In this chapter, you'll learn how to print
messages on the C-128 screen, how to use ASCII characters in assem
bly language programs, and how to perform a number of other neat
tricks in 8502 assembly language. We're going to accomplish these
feats using some new and fairly advanced assembly language pro
gramming techniques, along with a few not-so-new variations that
were covered in earlier chapters.
These are some of the new programming techniques discussed
in this chapter:
Q.S Program
We'll start with listing 7-1, a program called Q.S. It is written on a
Commodore 128 using the Merlin 128 assembler/editor system. Al
though minor modifications might be needed, it can be typed and
assembled using any assembler compatible with the Commodore 128
or the Commodore 64.
Listing 7-1 1 *
Q.S program 2 * Q. S
3 *
4 0R6 $1300
5 *
6 BUFLEN EQU 23
7 BSOUT EQU $FFD2
8 *
9 JMP BEGIN
10 *
11 TEXT DFB 87,72,69,82,69,32,73,83
12 DFB 32,84,72,69,32,67,79,77
13 DFB 77,79,68,79,82,69,63
14 *
For a Loop 115
When you type, assemble, and execute the program, it will print
a cryptic message on your video screen.
The first time the program reaches line 17, there is a 0 in the X
register (because we've just loaded a 0 into the X register). So the first
time the program encounters the statement LDA TEXTfX, the accu
mulator is loaded with the hexadecimal number $54, the ASCII code
for the letter T. So, in line 18, the BSOUT kernel routine takes the hex
number $54 from the accumulator, recognizes it as the ASCII code for
a T, and prints a T on the screen.
(Incidentally, we don't need # symbols before the numbers in
lines 11-13 because numbers that follow the DFB pseudo op and its
variants are automatically interpreted as literal numbers.)
Now let's move on to line 19. The mnemonic you see there—
INX—means "increment the X register." The first time the program
makes its way through the loop that starts at line 17, the X register
holds a 0. But as soon as the BSOUT routine has printed its first
character on the screen, the INX instruction in line 19 increments
that 0 to a 1.
Next, in line 20, is the instruction CPX #BUFLEN. Look back at
line 6, and you'll see that BUFLEN is a constant that equates to the
number 23. So the instruction CPX #BUFLEN means "compare the
value in the X register to the literal number 23." We perform this
comparison to determine whether 23 characters have been printed on
the screen. There are 23 characters in the text string that we're
printing, and when we've printed all of them, we want to print a
carriage return and end our program.
Comparison Instructions
There are three comparison instructions in 6502/8502 assembly lan
guage: CMP, CPX, and CPY. CMP means "compare to a value in the
accumulator." When you use the instruction CMP, followed by an
operand, the value expressed by the operand is subtracted from the
value in the accumulator. This subtraction operation is not performed
to determine the exact difference between these two values; it merely
determines whether or not they are equal and, if they are not equal,
which one is larger.
If the value in the accumulator is equal to the tested value, the
zero flag of the processor status register is set to 1. If the value in the
accumulator is not equal to the tested value, the zero flag is left in a
cleared state. If the value in the accumulator is less than the tested
value, then the carry flag of the processor status register is left in a
clear state. And if the value in the accumulator is greater than or
equal to the tested value, the zero flag is set to 1 and the carry flag is
set.
CPX and CPY work like CMP, except they are used to compare
values with the contents of the X and Y registers. They have the same
effects that CMP has on the status flags of the processor status regis
ter.
For a Loop 117
Table 7-1
Instruction Meaning
8502 Branching
Instructions BCC Branch if the carry (C) flag of the processor status (P)
register is clear. If the carry flag is set, the operation will
have no effect.
BCS Branch if the carry (C) flag is set. If the carry flag is clear,
the operation will have no effect.
BEQ. Branch if the result of an operation is zero, if the zero (Z)
flag is set.
BMI Branch on minus if an operation results in a set negative
(N) flag.
BNE Branch if not equal to zero, if the zero (Z) flag isn't set.
BPL Branch on plus if an operation results in a cleared negative
(N) flag.
BVC Branch if the overflow (V) flag is clear.
BVS Branch if overflow (V) flag is set.
JMP $C000
A Branching Program
Unfortunately, branching instructions are more complicated than
jump instructions. Listing 7-2 is a program that uses the BCC branch
ing instruction, which means "branch if carry set." The program is
called BRANCHIT.S, for obvious reasons.
1300: A9 05 5 LDA #5
1302: 18 6 CLC
1303: 6D 00 OC 7 ADC WHAZIS
1306: B0 01 8 BCS RETURN
1308: AA 9 TAX
1309: 60 10 RETURN RTS
1306:B0 01
The first figure in this line—1306—is the memory address where the
BCS instruction is stored when it is assembled into machine lan
guage. The second figure in the line—B0—is the machine language
equivalent of the BCS instruction. The third number—01—is an offset
value that must be computed by the Commodore 128's 8502 chip
before it can carry out the BCS instruction.
Something Fancy
As you can see, a very neat trick has been used to overcome the
distance limitations of a branching instruction. In this version of the
BRANCHIT.S program, the BCS instruction that appeared in the orig
inal program is replaced by a BCC instruction. And a new line,
containing a JMP instruction that can jump to any address in mem
ory, is inserted following the line containing the BCC instruction. In
this version of the program, if adding 5 to the value of WHAZIS
results in a carry, the program jumps to an address labeled
FARJUMP, which can be situated anywhere. Otherwise, the program
jumps to line 10, labeled CONT (for "continue"), and proceeds as
before.
This all sounds very complicated, and, until you get the hang of
it, it may be. But after you understand the general concept of condi
tional branching, you can use a simple table for writing conditional
branching instructions, as shown in table 7-2.
Listing 7-5 1 *
4 ORG $1300
5 EOL EQU 13
6 BUFLEN EQU 24
7 FILLCH EQU $20
8 BSOUT EQU $FFD2
9
10 JMP START
11 *
23 * STORE MESSAGE IN BU
24 *
25 LDX #0
26 L00P1 LDA TEXT,X
27 STA TXTBUF,X
28 CMP #EOL
29 BEQ PRINT
30 INX
31 CPX #BUFLEN
32 BCC LOOP1
33 *
34 * PRINT MESSAGE
35 *
36 PRINT LDX #0
37 L00P2 LDA TXTBUF,X
38 PHA
39 JSR BSOUT
40 PLA
41 CMP #EOL
42 BNE NEXT
43 JMP FINI
44 NEXT INX
45 CPX #BUFLEN
46 BCC L00P2
47 *
48 FINI RTS
49
50 TXTBUF DS BUFLEN
51 END
124 Principles and Techniques of Assembly Language Programming
Text Strings
The most obvious difference between the Q.S and A.S programs is
the way they handle text strings. In the Q.S program, we used a text
string made up of ASCII codes. There's also a text string in A.S, but
it's made up of actual characters. Because of that difference, A.S is a
much easier program to write than Q.S—and it's much easier to read
and understand.
Another important difference between A.S and its predecessor
is the way we write the loop that reads the characters. In Q.S, the
loop counts the number of characters that have been printed on the
screen, and ends when the count reaches 23. That's a perfectly good
system—for printing text strings that contain 23 characters. Unfortu
nately, it isn't so great for printing strings of other lengths. So it isn't a
very versatile routine for printing characters on a screen.
matter what characters they are. So, after you write a few subrou
tines that fill and then process a buffer in some manner, those sub
routines can be used for many different purposes. A buffer can
therefore serve as a central repository for text strings, which are then
easily accessible in many different ways.
Before you use a text buffer, though, it's always a good idea to
clear it; otherwise, it might be cluttered with leftover characters. So a
buffer-clearing routine is included in the A.S program. It's a short and
simple routine, but it does the job. It will clear a text buffer—or any
other block of memory that doesn't exceed its length limitations—and
fill the buffer with spaces, zeros, or any other value you might
choose. In the A.S program, the routine fills the buffer with a string
of spaces, which appear as blank spaces on your computer screen.
As you continue to work with assembly language, you'll find
that memory-clearing routines such as this one can come in very
handy in different kinds of programs. Word processors, telecommu
nications programs, and many other kinds of software packages make
extensive use of routines that clear values from blocks of memory
and replace them with other values.
The memory-clearing routine in the A.S program is not very
complicated. Using indirect addressing and an X register countdown,
it fills each memory address in a text buffer (TXTBUF) with a desig
nated "fill character" (FILLCH). Then the program ends.
The buffer-clearing routine in A.S works with any 8-bit fill char
acter, and with any buffer length (BUFLEN) up to 255 characters.
Later in this book, you'll find some 16-bit routines that can fill longer
blocks of RAM with values.
Listing 7-6 1 *
4 ORG $1300
5 *
14 JMP START
15 *
16 TXTBUF DS 40
17 4c
42 PRINT LDY #0
43 SHOW LDA (TEMPTR),Y
44 CMP #EOF
45 BEQ DONE
46 PHA
47 JSR BSOUT
48 PLA
49 CMP #EOL
50 BNE NEXT
51 JMP DONE
52 NEXT INY
53 CPY 0BUFLEN
54 BCC SHOW
55 DONE RTS
56 •
70 * PRINT 'HELLO ■ ■ ■
1
71 *
72 LDA #<HELLO
73 STA TEMPTR
74 LDA #>HELLO
75 STA TEMPTR+1
76 JSR PRINT
77
78 * PRINT •WHAT IS YOUR NAME?1
79 *
prints a short message on your screen and then waits for you to type a
response. If you type a response that the program considers incorrect,
it prompts you to try again. When you finally enter the line the
program is looking for, you get a "reward" message and the program
ends.
72 LDA #<HELLO
73 STA TEMPTR
74 LDA #>HELLO
75 STA TEMPTR+1
You can probably figure out what this sequence does without too
much difficulty. In source code produced by the Merlin 128 assem
bler and the Commodore 64 assembler, the string #< HELLO means
"the low byte of the address labeled HELLO" and the string
#>HELLO means "the high byte of the address labeled HELLO."
(These strings don't mean the contents of the address, by the way,
130 Principles and Techniques of Assembly Language Programming
Figure 8-1
Betore ASL
ASL instruction
c 7 6 5 4 3 2 1 0 Bit Positions
1' 0 1 1 0 1 0 1 1 0
(literal zero)
Bit Contents
After ASl
c 7 6 5 4 3 2 1 0 Bit Positions
0 1 1 | 0 1 0 1 1 0 Bit Contents
(literal zero)
Listing 8-1 10 ;
Multiplying by 2 20 *=$1 300
using ASL 30 •
If you run the program shown in listing 8-1, and then use the
C-128 monitor to examine the contents of memory address $FA,
you'll see that the number $40 (0100 0000) has been doubled to $80
(1000 0000) before being stored in memory address $FA.
Another use for the ASL instruction is to "pack" data, thus
increasing the computer's effective memory capacity. Later in this
chapter, there is an example of how to pack data using the ASL
instruction.
Figure 8-2
Before LSR
LSR instruction
7 6 5 4 3 2 1 0 C Bit Positions
0 0 1 1 0 1 0 1 1 0 Bit Contents
(literal
zero)
After LSR
7 6 5 4 3 2 1 0 C Bit Positions
0 0 1 1 0 1 0 1 1 Bit Contents
(literal
zero)
Listing 8-2 10 ■
40 VALUE1=$FA
50 VALUE2=$FB
60 •
70 *=$1300
80 # ■
Listing 8-3 10 ■
40 VALUE1=$FA
50 VALUE2=$FB
60 FLGADR=$FC
70 t ■
80 *=$1300
90 f ■
220 FLAG
240 ROL FLGADR
250 RTS AND END THE PROGRAM
0 1 1 0 1 0 1 1 Bit Contents
JT"U
After ROL
7 6 5 4 3 2 1 0 Bit Positions
1 1 0 1 0 1 1
• 1 Bit Contents
ROL, like ASL, can be used to shift the contents of the accumu
lator or a memory register one place to the left. But ROL does not
place a 0 in the bit 0 position. Instead, it rotates the carry bit into bit 0
Programming Bit by Bit 137
of the register being shifted. Then it moves every other bit in that
register one place to the left, rotating bit 7 into the carry bit. If the
carry bit is set when that happens, a 1 is placed in the bit 0 position of
the byte being shifted. If the carry bit is clear, a 0 goes into the bit 0
position of the shifted register.
Figure 8-4
Before ROR
ROR instruction
7 6 5 4 3 2 1 0 Bit Positions
0 1 1 0 1 0 1 1 Bit Contents
After ROR
4 3 2 Bit Positions
1 1 Bit Contents
■a-
Logical Operators
Before we move on to our next topic—binary arithmetic—let's take a
brief glance at four important assembly language mnemonics called
logical operators. These instructions are AND ("and"), ORA ("or"),
EOR ("exclusive or"), and BIT ("bit"). (The BIT instruction is dis
cussed at the end of this chapter.)
The four 8502 logical operators look very mysterious at first
glance. But, in typical assembly language fashion, they lose much of
their mystery after you understand how they work.
AND, ORA, EOR, and BIT are all used to compare values. But
they work differently from the comparison operators CMP, CPX, and
CPY. The instructions CMP, CPX, and CPY all yield general results;
they can only determine whether two values are equal and, if the
values aren't equal, which one is larger. AND, ORA, EOR, and BIT
are more specific instructions. They're used to compare single bits of
numbers, and hence have all sorts of uses.
The four logical operators in assembly language use the princi-
138 Principles and Techniques of Assembiy Language Programming
AND Operator
The operator AND in assembly language has the same meaning as the
word and in English. If one bit AND another bit have a value of 1
(and are thus "true"), then the AND operator also yields a value of 1.
But if any other condition exists—if one bit is true and the other is
false, or if both bits are false—the AND operator returns a result of 0,
or false.
The results of logical operators are often illustrated with dia
grams called truth tables. Figure 8-5 is a truth table for the AND
operator.
Figure 8-5 0 0 1
AND truth table AND 0 AND 1 AND 0 AND 1
0 0
Figure 8-6 shows the AND operation that takes place if the two
lines of code in listing 8-4 are included in a program.
ORA Operator
When the instruction ORA ("or") compares a pair of bits, the result
of the comparison is 1 (true) if the value of either bit is 1. Figure 8-7 is
a truth table for the ORA instruction.
Figure 8-7 0 0 1 1
ORA truth table ORAO ORA 1 ORAO ORA 1
0 1
EOR Operator
The instruction EOR (which stands for "exclusive or") returns a true
value (1) if one—and only one—of the bits in the pair being tested is a
1. Figure 8-9 is a truth table for the EOR operator.
Figure 8-9 0 0 1 1
EOR truth table EOR0 EOR 1 EOR0 EOR 1
0 1 0
Operation 2
Restoring the Original Number
Packing Data
To get an idea of how data packing works, suppose that you have a
series of 4-bit values stored in a block of memory. These values could
be ASCII characters, BCD numbers, or just about any other kinds of
4-bit values.
Using the ASL instruction, you could pack two such values into
every byte of the block of memory. Thus, you would store the values
in half the memory space that they had previously occupied in their
unpacked form. Listing 8-6, PACKDATA, is a routine that can be
used to pack bytes of data in a program.
Listing 8-6 10 •
60 NYB1 = $FA
70 NYB2=$FB
80 PKDBYT=$FC
90 ■
150 CLC
160 LDA NYB1
170 ASL A
180 ASL A
190 ASL A
200 ASL A
210 ORA NYB2
220 STA PKDBYT
230 RTS
The routine in listing 8-6 loads a 4-bit value into the accumula
tor, shifts that value to the high nibble in the accumulator, and then-
using the ORA logical operator—places another 4-bit value in the low
nibble of the accumulator. Thus, the accumulator is "packed" with
two 4-bit values, and those two values are stored in PKDBYT, a single
8-bit memory address.
Type the PACKDATA program, and execute it using the assem
bler's machine language monitor. After you run the program, use the
C-128 monitor to peek into the computer's memory to see exactly
what has happened. Just type the monitor command:
M $FA
142 Principles and Techniques of Assembly Language Programming
(for "display memory address $FA"), and the computer will respond
with a line showing you what the program did. If all has gone well,
the number $04 is stored in memory address $FA and the number
$06 is stored in memory address $FB. And both of these values are
packed together and stored in memory address $FC.
It doesn't take much imagination to see how this technique can
increase your computer's capacity to store 4-bit numbers or ASCII
characters, which can be stored in memory in the form of 4-bit
numbers. By packing data, you can double the text storage capacity
of an 8-bit computer, because you can store two characters in each 8-
bit register in the computer's memory.
Unpacking Data
It wouldn't do much good to pack data if it couldn't later be un
packed. It so happens that data packed using ASL can be unpacked
using the complementary instruction LSR (logical shift right) and the
logical operator AND. Listing 8-7 is a program called UNPACKIT,
which can unpack data using a series of LSR instructions.
Listing 8-7 10 ;
Unpacking data 20 ; UNPACKIT
using LSR 30 ;
40 PKDBYT=$FA
50 LOWBYT=$FB
60 HIBYT=$FC
70 ;
80 *=$1300
90 ;
100 LDA #255 ;0R ANY OTHER 8-BIT VALUE
110 STA PKDBYT
120 LDA #0 ;CLEAR LOWBYT AND HIBYT
130 STA LOWBYT
140 STA HIBYT
150 ;
160 LDA PKDBYT
170 AND #$0F ;BINARY 0000 1111
180 STA LOWBYT
190 LDA PKDBYT
200 LSR A
210 LSR A
220 LSR A
230 LSR A
240 STA HIBYT
250 RTS
four bits of this packed byte are then filled with zeros using the
logical operator AND. Then the lower nibble of the byte is stored into
a memory register called LOWBYT.
Then, the accumulator is loaded for a second time with the
packed byte. This time the byte is shifted four places to the right
using the instruction LSR. The result of this maneuver is a 4-bit value
that is finally stored in a memory register called HIBYT. The packed
value in PKDBYT has thus been split, or "unpacked," into two 4-bit
values—one stored in LOBYT and the other in HIBYT. And each of
these 4-bit numbers (which may represent an ASCII character or any
other 4-bit value) can now be processed separately.
BIT Operator
That brings us to the BIT operator, an instruction that's a little more
complicated than AND, ORA, or EOR. The BIT instruction deter
mines whether the value stored in a memory address matches a value
stored in the accumulator. The BIT instruction can be used only with
absolute or zero page addressing—in other words, using either of
these formats:
BIT $02A7
BIT $FB
register. This can be a useful thing to know because bit 6 and bit 7 are
important flags in the 8502 chip's P register; bit 6 is the P register's
overflow (V) flag, and bit 7 is its negative (N) flag. Therefore, the BIT
instruction can be used as a quick and easy method for checking bit 6
or bit 7 of any 8-bit value. If bit 6 of the value being tested is set, the P
register's V flag will also be set, and a BVC or BVS instruction can
then be used to determine what will happen next in the program. If
bit 7 of the tested value is set, the P register's N flag will be set, and a
BPL or BMI instruction can be used to determine the outcome of the
routine.
It's also important to note that after all of these actions take
place, the value in the accumulator (and the memory location being
tested) always remain unchanged. So if you want to perform a logical
AND operation without disturbing the value of the accumulator or
the memory register being tested, the BIT mnemonic may be the best
instruction to use.
9
Assembly
Language
Math
How the C-128 adds,
subtracts, multiplies,
and divides
146 Principles and Techniques of Assembly Language Programming
In the first eight chapters, we've been doing a lot of reading, and a lot
of writing. Now it's time to do some arithmetic. In this chapter, you'll
learn how the Commodore 128 adds, subtracts, multiplies, and di
vides.
As we've seen in previous chapters, the Commodore 128 can
deal with many kinds of numbers—including binary, decimal, hex
adecimal, signed, and unsigned numbers. Other kinds of numbers
that the C-128 can handle include binary coded decimal numbers
and floating-point decimal numbers. We're going to take a cursory
look at each of these types of numbers—and maybe a few other
kinds, too.
To understand how the C-128 works with numbers, it is essen
tial to have a fairly good understanding of the busiest flag in the 8502
microprocessor chip: the carry flag of the 8502's processor status
register. So let's take a close look at the 8502's carry flag now.
Figure 9-2 presents two problems that use larger (8-bit) num
bers. The first of these problems doesn't generate a carry, but the
second one does.
Note that in the second problem in figure 9-2, the sum is a 9-bit
number: 1 1000 1100 in binary, or #18C in hexadecimal notation.
Listing 9-1 is an assembly language program that performs this addi
tion problem. It is titled ADDNCARRY.
Assembly Language Math 147
memory addresses $FA and $FB using your C-128 monitor to see
whether the calculation in the ADDNCARRY2 program resulted in a
carry.
So let's do it! Assemble the program, execute it, and then use the
monitor to take a look at the contents of memory address $FA. The
monitor should tell you two things: that memory address $FA again
holds the value #$8C—the result of our ADDNCARRY2 calculation,
without its carry—and that the carry resulting from the calculation
now resides in memory register $FB.
Listing 9-3 10
16-bit addition 20 ;ADD16
program 30 ■
80 '*=$1300
90 ;
100 CLD
110 C LC
120 LDA $FA ;REM LOW HALF OF 16-BIT NUMBER
IN $FA AND $FB
130 ADC $FC ;REM LOW HALF OF 16-BIT NUMBER
IN $FC AND $FD
140 STA $0C00 ;LOW BYTE OF SUM
150 LDA $FB ;REM HIGH HALF OF 16-BIT
NUMBER IN $FA AND $FB
160 ADC $FD ;REM HIGH HALF OF 16-BIT
NUMBER IN $FC AND $FD
170 STA $0C01 ;HIGH BYTE OF SUM
180 RTS
The first thing the ADD 16 program does is clear the carry flag of
the P register. Then the program adds the low byte of a 16-bit number
in $FA and $FB to the low byte of a 16-bit number in $FC and $FD.
The result of this half of our calculation is placed in memory address
$0C00. If there is a carry, the P register's carry bit is set automati
cally.
In the second half of the program, the high byte of the number
in $FA and $FB is added to the high byte of the number in $FC and
$FD. If the P register's carry bit has been set as a result of the
preceding addition operation, then a carry is also added to the high
bytes of the two numbers. If the carry bit is clear, there is no carry.
When this half of our calculation has been completed, the result
is put into memory address $0C01. Then, finally, the results of our
completed addition problem are stored—low byte first—in memory
addresses $0C00 and $0C01.
Listing 9-4 10 ;
16-bit subtraction 20 SUB16
program 30
30 THIS PROGRAM SUBTRACTS A 16-BIT NUMBER
IN $FA AND $FB
40 FROM A 16-BIT NUMBER IN $FC AND $FD
50 AND DEPOSITS THE RESULTS IN $0C00 AND
$0C00
60
70 *=$1300
80
90 CLD
100 SEC ;REM SET CARRY
110 LDA $FC;REM LOW HALF OF 16-BIT NUMBER
IN $FC AND $FD
150 Principles and Techniques of Assembly Language Programming
Listing 9-4 cont. 120 SBC $FA;REM LOW HALF OF 16-BIT NUMBER
IN $FA AND $FB
130 STA $0C00 ;LOW BYTE OF THE ANSWER
140 LDA $FD ;REM HIGH HALF OF 16-BIT
NUMBER IN $FC AND $FD
150 SBC $FB ;REM HIGH HALF OF 16-BIT
NUMBER IN $FA AND $FB
160 STA $0C01 ;HIGH BYTE OF THE ANSWER
170 RTS
Multiplying Numbers
Binary numbers are multiplied in the same way as decimal numbers.
Unfortunately, though, the 6502/8502 instruction set contains no spe
cific instructions for multiplication or division. To multiply a pair of
numbers using 6502/8502 assembly language, you have to perform a
series of addition operations, as shown in figure 9-3. To divide num
bers, you have to perform subtraction sequences.
0011110 ($1E)
uct to the right before adding it to the new one. Listing 9-5 demon
strates how this process works.
Listing 9-5 10 ;
Multiplication 20 ; MULT A
program 30 ;
40 MPR = $FC .-MULTIPLIER
50 MPD1=$FD .-MULTIPLICAND
60 MPD2=$0C00 ;NEW MULTIPLICAND AFTER 8
SHIFTS
70 PR0DL=$0C01 ;LOW BYTE OF PRODUCT
80 PRODH=$0C02 ;HIGH BYTE OF PRODUCT
90 ;
100 *=$1300
110 ;
120 ;THESE ARE THE NUMBERS WE WILL MULTIPLY
130 ;
140 LDA #250
150 STA MPR
160 LDA #2
170 STA MPD1
180 ;
190 MULT CLD
200 CLC
210 LDA #0 ;CLEAR ACCUMULATOR
220 STA MPD2 ;CLEAR ADDRESS FOR SHIFTED
MULTIPLICAND
230 STA PRODH ;CLEAR HIGH BYTE OF PRODUCT
ADDRESS
240 STA PRODL ;CLEAR LOW BYTE OF PRODUCT
ADDRESS
250 LDX #8 ;WE WILL USE THE X REGISTER AS
A COUNTER
260 LOOP LSR MPR ;SHIFT MULTIPLIER RIGHT;
LSB DROPS INTO CARRY
270 BCC NOADD ;TEST CARRY BIT; IF ZERO,
BRANCH TO NOADD
280 LDA PRODH
290 CLC
300 ADC MPD1 ;ADD HIGH BYTE OF PRODUCT TO
MULTIPLICAND
310 STA PRODH ;RESULT IS NEW HIGH BYTE OF
PRODUCT
320 LDA PRODL ;LOAD ACCUMULATOR WITH LOW
BYTE OF PRODUCT
330 ADC MPD2 ;ADD HIGH PART OF
MULTIPLICAND
340 STA PRODL ;RESULT IS NEW LOW BYTE OF
PRODUCT
152 Principles and Techniques of Assembly Language Programming
Listing 9-6 10 ;
Improved 20 ;MULTB
multiplication 30 ; (AN IMPROVED MULTIPLICATION PROGRAM)
program 40 ;
50 PRODL=$FC
Assembly Language Math 153
100 * = $1 300
110 f ■
170 LDA #0
180 STA PRODH
190 LDX #8
200 LOOP LSR MPR
210 BCC NOADD
220 CLC
230 ADC MPD
240 NOADD ROR A
250 ROR PRODH
260 DEX
270 BNE LOOP
280 STA PRODL
290 RTS
If you like, you can test the MULTB multiplication program the
same way you tested the previous one: by executing it using your
machine language monitor, and then using your monitor to take a
look at its results.
Play around with these two multiplication problems, trying out
different values and seeing how these values are processed in each
program. The more you experiment with binary multiplication pro
grams, the better you'll understand them. And the better you under
stand binary addition, subtraction, and multiplication problems, the
more understanding you'll have of 6502/8502 assembly language.
One of the best ways to become familiar with how binary multi
plication works is to do a few problems by hand—using those two
tools of our forefathers, a pencil and a piece of paper. Work enough
binary multiplication problems on paper, and you'll soon begin to
understand the principles of 6502/8502 multiplication.
Dividing Numbers
Just as subtraction is a reverse form of addition, division is a reverse
form of multiplication. So it should come as no surprise that the 8502
chip, which has no specific instructions for multiplying numbers,
also lacks specific instructions for dividing.
Still, it is possible to perform division—even multiprecision long
154 Principles and Techniques of Assembly Language Programming
Listing 9-7 10 ■
40 *=$1300
50 ■
Signed Numbers
Before we move to the next chapter, it might be a good idea to pause
for a moment and take a quick look at signed numbers. To represent
a signed number in binary arithmetic, the leftmost bit (bit 7) repre
sents a positive or negative sign. In signed binary arithmetic, if bit 7
of a number is a 0, the number is positive. But if bit 7 is a 1, the
number is negative.
If you use one bit of an 8-bit number to represent its sign, you
no longer have an 8-bit number. What you then have is a 7-bit
number—or, expressed another way, you have a signed number that
can represent values from —128 to +127, instead of 0 to 255.
It takes more than redesignating a bit to turn unsigned binary
arithmetical operations into signed binary arithmetical operations.
Consider, for example, what would happen if we tried to add the
numbers +5 and —4 by doing nothing more than using bit 7 as a
sign, as shown in figure 9-4.
Oops! That's wrong, too! The answer should be +3. Well, that
takes us back to the drawing board. Ones complement arithmetic
doesn't work.
fashion, the C-128 manages to keep its page zero and stack addresses
free for use by its 8502 chip, simultaneously providing its Z-80 chip
with a page zero and a stack of its own.
A memory map of the C-128 when it is operated in C/PM mode
is shown in figure F-3 (appendix F). In this mode, the C-128 uses two
blocks of memory, each with a capacity of 64K. On the memory map
in figure F-3, these blocks of memory are labeled bank 0 and bank 1.
The largest single block of memory on the CP/M map is a 56K
segment of RAM called the transient program area, or TPA. This block
of memory, which occupies most of memory bank 1, is where CP/M
programs and operating system commands are stored in the C-128's
memory. Bank 1 also contains the CP/M page zero, plus two blocks of
memory that it shares with bank 0: a small memory management unit,
or MMU, and a memory segment called the BDOS and BIOS common
area.
The MMU, which is also used when the C-128's 8502 chip is
active, resides at the very top of the computer's memory. We'll take a
closer look at it later in this chapter. BDOS, which stands for basic
disk operating system, is a CP/M utility that handles file-based disk
operations and also has limited screen editing and printing capabili
ties. BIOS, which stands for basic input/output system, is a CP/M utility
that manages simple input and output operations.
Memory bank 0 also contains the C-128's CP/M ROM, which
extends from $00100 to $0FFF (as far as the Z-80 chip is concerned);
some special-purpose RAM blocks used for key code tables and
screen displays; a buffer in which a CCP (command interpreter) is
stored; and the BDOS and BIOS memory area that is shared with
bank 1.
As we have seen in previous chapters, the main reason that
CP/M capabilities were included in the C-128 was to give C-128 users
access to the tremendous amount of CP/M business software. It is
possible to write CP/M programs on a Commodore 128, and Commo
dore even offers a CP/M programming kit (available at extra cost) to
users who are interested in writing C-128 programs in Z-80 assembly
language. But the C-128 was not designed primarily as a CP/M
software development system and, because there isn't too much
CP/M software being developed these days, it is unlikely that many
C-128 owners have much interest in doing Z-80 assembly language
programming. So, instead of spending any more time discussing
CP/M memory, let's move on to the C-128's other two operating
modes, which will probably be of much greater interest to most
C-128 users.
special registers are also active when the Commodore 128 is in its
native C-128 mode. However, because their original design was
based on the memory configuration of the Commodore 64, they are
used more frequently in Commodore 64 programming than they are
in programs designed for the Commodore 128.
In the following section, we will see how memory registers
$0000 and $0001 are used in Commodore 64 assembly language pro
grams. Later, we will see how they are used in Commodore 128
programs.
are significant. When an R6510 bit is set, the function that it controls
becomes an output function. When a bit is cleared, the function that
it controls becomes an input function.
Bits 6 and 7 of the R6510 register, like the corresponding bits of
the D6510 register, are not significant. Bits 3 through 5, like the same
bits of the D6510 register, are used to control the Commodore 64/128
data cassette recorder. That leaves only three bits—bits 0 through 2—
for memory control purposes, but these are three of the most power
ful bits on the Commodore's memory map.
Table 10-1 shows the six significant bits of the R6510 and D6510
registers, along with their functions.
Table 10-1
Functions of the
R6510 and D6510 o LORAM On: $A000-$BFFF is
Registers BASIC ROM
Off: $A000-$BFFF is RAM
1 HIRAM 1 (Output) On: $E000-$FFFF is
kernel ROM
Off: $E000-$FFFF is RAM
2 CHAREN 1 (Output) On: $D000-$DFFF is I/O
ROM
Off: $D000-$DFFF is
character ROM
1 (Output) On: Write to cassette line
Off: Read from cassette
line
0 (Input) On: Cassette switch
pressed
Off: Cassette switch not
pressed
1 (Output) On: Cassette motor on
Map Reading
In the Commodore 64, the D6510 and R6510 registers work hand in
hand with four preset memory configurations called memory banks.
The C-128 also uses these four memory banks, though in a slightly
different manner. We'll see how all of this works in the second half of
this chapter.
First, though, here's a simplified explanation of the Commodore
128/64 memory map. It covers most of the important memory blocks
in the computer's four main memory banks.
Table 10-2
Memory
C-128's Page Zero Addresses Register's Function
Memory Map
$00-$01 Special 8502 I/O control registers (the I/O port data direction
register and the I/O port data register)
$02-$D6 Used by BASIC and the C-128 operating system
$D7 Screen width flag (0 = 40 columns, 128 = 80 columns)
$D8 40-column text/graphics mode flag (224 is graphic 4, 160 is
graphic 3, 96 is graphic 2, 32 is graphic 1, 0 is graphic 0)
$D9 Shadow register for CHAREN bit of memory address $0001
(the 8502 I/O port data register). 4 is I/O ROM at $D000-
$DFFF, 0 is character ROM at $D000-$DFFF.
$E0-$F9 Flags used in windowing, screen editing, and keyboard
reading operations
$FA-$FE Bytes left free for user-written programs
$FF Used by BASIC interpreter
As table 10-2 illustrates, there are only five bytes on page zero
that have been left free for use in user-written programs—$FA, $FB,
$FC, $FD, and $FE. Despite this scarcity of page zero space, how
ever, a sharp-eyed programmer can usually find other zero page
addresses that can be safely used in assembly language programs. For
example, many of the addresses on page zero are reserved for use by
the C-128's built-in BASIC 7.0 interpreter, and a number of others are
only used by the floating-point math routines built into the com
puter's operating system. Many of the registers in these categories
can be used by user-written programs, if the programs aren't de
signed to be called from BASIC and don't require the C-128's floating
point math package.
Detailed C-128 memory maps can be found in a number of
reference volumes, including the Commodore 128 Reference Guide for
Programmers, written by David L. Heiserman and published by How
ard W. Sams & Co., Inc.
memory that the Commodore 128 uses to keep track of the return
addresses of machine language subroutines and interrupts (tempo
rary interruptions in normal program processing). The stack is also
used for temporary storage of the values of memory registers during
operations that would otherwise change those values and thus de
stroy them. The stack is frequently used by the C-128 operating
system, and is also available for use in user-written programs.
Character Sets
The Commodore 64 and the Commodore 128 each have two built-in
character sets. One character set, which contains uppercase letters
and graphics characters, begins at memory address $D000 in both the
Commodore 64 and the Commodore 128. The other set includes
uppercase and lowercase letters but no graphics characters. It starts
at $D800 in both machines. Complete listings of both character sets
can be found in appendix C and appendix D.
In the C-64 and C-128, either or both character sets can be
copied from ROM into RAM. After a character set is copied into
RAM, it can be modified in any way that the programmer desires.
Copying a character set into RAM, and then modifying it, is the main
topic for discussion for the rest of this chapter.
Because the memory architecture of Commodore 128 is based
on the memory architecture of the Commodore 64, understanding
how memory transfer operations work in the C-64 will help us under
stand how the same kinds of operations work in the C-128. So now
let's take another look at the Commodore 64 memory map.
Four-Bank C-64
The memory map of the C-64 (shown in figure F-5 in appendix F) can
be divided into four memory banks. The reason for this arrangement
is that the VIC-II chip, which generates the 40-column screen display
of both the C-128 and its built-in C-64, can access only 16K of mem
ory at a time. By dividing the C-64's 64K of memory into four banks
170 Principles and Techniques of Assembly Language Programming
of 16K each, the engineers who designed the C-64 gave the VTC-II
chip a choice of four memory banks to look at. By setting certain bits
in certain memory registers, you can tell the VIC-II chip which bank
of memory to access, and which starting address in that memory to
look at, to get the data needed to produce screen displays.
Binary Decimal/Hexadecimal
0 $0000-$3FFF 00 00
1 $4000-$7FFF 01 01
2 $8000-$BFFF 10 02
3 $C000-$FFFF 11 03
An Important Warning
There's one important note of caution regarding the use of table 10-3.
Memory register $DD00 is a multipurpose register that controls vari
ous input/output functions of the 8502 chip and designates the mem
ory bank that will be used by the VIC-II chip. So, when you load a
value from table 10-3 into bits 0 and 1 of register $DD00, don't
disturb the contents of the other bits in the register. To alter bits 0
and 1 without disturbing bits 2 through 7, you can use an assembly
language routine such as the one shown in listing 10-1.
RAM, store the proper code in bits 0 through 3 of the VMCSB regis
ter, as illustrated in table 10-4.
"This memory block is not normally available for storage of character data.
tThis block is where ROM character images are stored; RAM stored in this block is
not visible to the VIC-II chip, and thus cannot be used for storage of user-generated
character sets or any other kinds of graphics data.
As you can see by examining table 10-4, any character set cop
ied into RAM—whether it's a full set or a partial set—must begin at a
memory address that's evenly divisible by $800—that is, on a 2K
boundary. Table 10-4 shows every possible starting address for a C-64
character set. As we shall see later, the Commodore 128 uses the
same set of character set starting addresses, but handles them a little
differently.
As table 10-4 also shows, the meaning of the value stored in bits
0 through 3 of the C-64's VMCSB register can vary, depending upon
which bank of memory the VIC-II chip is looking at.
Now we're ready see how a character set can be copied from
RAM into ROM in the Commodore 64. Then we'll see how a very
similar process can be used to move a Commodore 128 character set
from ROM into RAM.
*This memory block is not normally available for storage of screen memory data.
tThis block are where ROM character images are stored; RAM stored in this block
is not visible to the VIC-II chip, and thus cannot be used for storage of other kinds
of graphics data.
tThis is the default storage area for color memory. This memory block is not large
enough for storage of a high-resolution screen map, and must be used to store color
data when the C-64 is in text mode. So this address is normally not available for
storage of screen map data in either text mode or bit-mapped mode.
When the values shown in table 10-5 are stored in the VMCSB
register, care must be taken not to disturb the value of the lower
nibble of the register because that nibble controls the location of the
C-64 character set. A masking routine like the one shown in listing
10-3 can be used to change the upper nibble of VMCSB without
changing the register's lower nibble.
Memory Magic 173
Add all of that RAM and ROM, and you get a grand total of
196K. And that's not all that the Commodore 128 offers. In addition
to the two 64K RAM banks that are built into every C-128, two more
64K RAM banks can be installed, for a total of 256K of RAM. It's also
possible to add up to 64K of additional ROM—32K in the form of
plug-in cartridges, and 32K that can be installed on the main circuit
board. So the C-128, which has 196K of memory as soon as you take
it out of the box, can be expanded (theoretically, anyway) into a 388K
machine!
Those are impressive figures, especially when you consider that
the 8502 chip used in the C-128, like the 6510 chip built into the C-64,
is an 8-bit microprocessor. As we saw in chapter 3, an 8-bit
microprocessor can address only 64K of memory at a time. So, even
though the C-128 can store large amounts of data in its memory, it
can't manipulate all of that data simultaneously.
Bank Switching
To handle the vast number of bytes that it can store, the C-128 relies
on a rather sophisticated programming technique called bank switch
ing. This technique (which was also used to expand the Apple lie and
174 Principles and Techniques of Assembly Language Programming
the Apple lie into 128K 8-bit computers) is illustrated in figures F-l
and F-2 (in appendix F).
When the Commodore 128 is operated in C-128 mode, its mem
ory can be divided into three blocks: two 64K blocks of RAM and one
48K memory block made up almost completely of ROM. On the
screen map shown in figure F-l these segments are labeled block A,
block B, and block C. (Technically, the C-128 also has two additional
RAM blocks, but both of these blocks were designed for future ex
pansion; in an unexpanded C-128, they are identical to RAM blocks A
and B.)
As figure F-l also shows, RAM block A, RAM block B, and
ROM block C all share a small strip of RAM at the very top of the
C-128's memory. This segment of memory is the memory manage
ment unit (MMU). It's only five bytes long—it extends from memory
address $FF00 to address $FF04—but it manages all of the C-128's
bank-switching operations. Because the MMU can be accessed from
any block of memory, it can be used as a main switching station,
moving from one memory block to another as it keeps watch over all
of them simultaneously.
Actually, the configuration register at $FF00 is a RAM copy of
an I/O register at $D500. Functionally, these two registers are identi
cal; writing a value to either one of them automatically writes the
same value to the other. But using the register at $FF00 is usually
better than using the one at $D500, because $FF00 is accessible to all
memory banks and $D500 is not. So $FF00 is referred to as the MMU
configuration register throughout the rest of this chapter.
At the bottom of the screen map in figure F-l, there's another
segment of RAM that's shared by blocks A and B. This portion of
memory, which extends from $0000 to $03FF, is occupied by page
zero ($0000-$00FF), the 8510 stack ($0100-$01FF), and a 512K block
of RAM vectors, important BASIC routines, and operating system
routines ($0200-$03FF). The $0000 through $03FF block of memory
contains RAM, so it isn't accessible to the C-128's ROM block. But its
contents are always available to RAM blocks A and B.
Three-Bank BASIC
An interesting fact about the C-128 is that its built-in BASIC 7.0
interpreter makes use of all three of the memory blocks illustrated in
figure F-l. Although the C-128's BASIC interpreter resides in the
ROM block, the RAM in which BASIC 7.0 programs are stored is in
block A, and the variables used in BASIC 7.0 programs are stored in
the 64K of free RAM that's available in block B. So, when the C-128 is
running a BASIC 7.0 program, the computer's MMU is almost con
stantly busy switching between one block of memory and another.
All of this MMU activity is usually quite transparent to the BASIC
programmer because the C-128 is designed to take care of BASIC'S
bank-switching needs automatically.
When the Commodore 128 is processing an assembly language
program, however, there is nothing automatic about bank switching.
Memory Magic 175
When an assembly language program is written for the C-128, it's the
programmer's responsibility to take care of all necessary bank-
switching operations.
Fortunately, though, with the help of memory maps such as
those shown in figures F-l and F-2, the concept of bank switching
isn't too difficult to understand. Because the 8502 chip can "see"
only 64K of memory at a time, it is up to the MMU to determine
whether the 8502 is looking at block A, block B, or the ROM block at
any given time. To help it carry out this task, the MMU is equipped
with one very special register called a configuration register, which is
situated at memory address $FF00. The configuration register has
eight bits, which function as follows:
00 BASIC ROM
00 BASIC ROM
11 RAM
• Bits 6 and 7 determine whether the 8502 will see RAM from
block A or RAM from block B in memory addresses $0000
through $FFEF. The settings of these bits are:
Table 10-6
Bank
The Four Most Number Addresses Contents
Important Memory
0 $0000-$FEFF RAM from block A
Banks Used by the
$FF00-$FF04 MMU
C-128 $FF05-$FFFF RAM from block A
COPYCHRS.BAS 7 REM
program 8 REM A PROGRAM TO MOVE THE C-128'S
CHARACTER SET FROM ROM INTO RAM
9 REM
10 DATA 255,129,129,129,129,129,129,255
15 REM
30 GRAPHIC 2,1:REM MOVE START OF BASIC UP
TO $4000
40 POKE 2604,PEEK(2604) AND 240 OR 8:REM
TELL VIC CHIP WHERE TO FIND NEW CHAR SET
50 FAST:REM SPEED UP CHAR-COPYING OPERATION
60 FOR L=0 TO 2047:BANK 14:C=PEEK(53248+L)
:BANK 0:POKE 8192+L,C:NEXT L:REM POKE
CHAR DATA INTO NEW LOCATION
70 SLOW:REM RESUME NORMAL CPU SPEED
80 COLOR 0,7:COLOR 4,7:C0L0R 5,2:REM SET
SCREEN, BORDER AND CHAR COLORS
90 FOR L=0 TO 7:READ S:POKE
8192+0*8+L,S:NEXT L:REM CHANGE '3' CHAR
TO A BOX
100 GRAPHIC 0,1:REM USE 40-COL TEXT MODE
110 PRINT "a";:REM USE REDEFINED '3' CHAR
AS A CURSOR
120 GETKEY A$:PRINT CHR$(20);:PRINT A$;:REM
GET INPUT, BACKSPACE TO COVER UP
CURSOR, AND PRINT TYPED CHAR ON SCREEN
130 GOTO 110:REM GET NEXT INPUT CHAR
memory address $4000 so that the new character set being created by
the program will not interfere with the program that is creating it.
In lines 20 and 40, the VIC-II chip is told the location of the new
character set. In lines 50 through 70, the C-128's ROM character set
is copied from bank 15 ROM into bank 0 RAM. The new character
set starts at RAM address $2000—a block of memory reserved for a
bit-mapped screen when high-resolution graphics are used, but free
for just about any other kind of use when BASIC is moved out of the
way and high-resolution graphics are not needed.
Type and run the COPYCHRS.BAS program, and you'll see that
it takes quite a long time to copy a character set using BASIC, even
when the speed of operation of the 8502 chip is increased with a
FAST instruction. Listing 10-5, titled COPYCHRS2.BAS, improves
matters considerably by breaking out of BASIC for a while and call
ing a machine language routine.
The third thing to remember when you have relocated page zero
is that when you write to an address in the block of memory to which
page zero has been moved, you're actually writing to the zero page
address that corresponds to the address you're writing to. For exam
ple, if page zero has been relocated to $1300 and the statement STA
$13C0 appears in a program, the value of the accumulator is stored
not in memory address $13C0 but in the zero page address $C0.
It is also important to remember that before the stack is relo
cated for use in a subroutine, the stack pointer should be saved
somewhere in memory before the new stack is used. Then, when the
subroutine that uses its own stack has been executed, the C-128's
original stack can be restored.
This brings us to the close of chapter 10. In chapter 11, we'll see
how to make a character-copying routine run even faster by con
verting the routine into a one hundred percent machine language
program.
Part Two
Assembly
Language Graphics
and Sound
Introducing
Commodore 128
High-Resolution
Graphics
And joystick operations
190 Assembly Language Graphics and Sound
Listing 11-1 cont. 150 FOR DEC = 1 TO 8:GOSUB 210:REM THIS LOOP
DRAWS THE BALL COMING DOWN
160 PSN=PSN+41:REM THE BALL COMES DOWN
170 IF CT>40 THEN 100:REM BALL OFF SCREEN—
LOOP BACK
180 NEXT DEC
190 GOTO 110:REM DONE — START AGAIN
200 REM **** PRINT BALL ON SCREEN ****
210 POKE PSN,BALL
220 FOR L=1 TO 50:NEXT L
230 POKE PSN,SPACE
240 CT=CT+1:RETURN
250 END
memory map \ j
0428—>■
0450 -
0478 —*-
0480 ~^r~
04F0 -
_ 0518 —
0540 •■
0500 °b6a^
O5B8—►
O5E°-5ii=q
0630-^^^—►
nrnn 0658~T
oro 06F8—H
0748—>
0770
0798 +
memory map I
D050 °828^
D878—*
D8AO •*
D8C8—-
Z --r
D9EO DMB8—
DA3O™*^
DA80.^!=:
DADO^=:
DB20J^—
DB98—-
The C-128's screen memory map and color memory map are
designed to be used together. In addition to its screen display codes,
the Commodore 128 also has a set of 16 color codes. By poking those
codes into the computer's color memory map, you can determine the
color of each individual text or graphics character that appears on the
screen. The color codes used by the Commodore 128 are shown in
table 11-1.
C-128 High-Resolution Graphics 193
Table 11-1
Code Color Code Color
Commodore 128's
Color Codes 0 Black 8 Orange
1 White 9 Brown
2 Red 10 Light Red
3 Cyan 11 Gray 1
4 Purple 12 Gray 2
5 Green 13 Light Green
6 Blue 14 Light Blue
7 Yellow 15 Gray 3
Table 11-2
Switch Value Binary Value Meaning
C-128 Hand
Controller Values 0 0000 0000 No action
1 0000 0001 Up
2 0000 0010 Down
3 0000 0011 None
4 0000 0100 Left
5 0000 0101 Left + up
6 0000 0110 Left + down
7 0000 0111 None
8 0000 1000 Right
9 0000 1001 Right + up
10 0000 1010 Right + down
11 0000 1011 None
12 0000 1100 None
13 0000 1101 None
14 0000 1110 None
15 0000 1111 None
16 0001 0000 Trigger button pressed
17 0001 0001 Trigger + up
18 0001 0010 Trigger + down
19 0001 0011 None
20 0001 0100 Trigger + left
21 0001 0101 Trigger + left + up
22 0001 0110 Trigger + left + down
23 0001 0111 None
24 0001 1000 Trigger + right
25 0001 1001 Trigger + right + up
26 0001 1010 Trigger + right + down
>26 >0001 1010 None
C-128 High-Resolution Graphics 197
40-Column Modes
When the C-128 is in 40-column text mode, it displays 25 lines of 40
typed characters each (a total of 1,000 characters) on its monitor
screen. Each of these characters is made up of eight bytes of binary
data.
In its 40-column high-resolution mode, the Commodore 128
produces a screen display 320 dots (or pixels) wide and 200 pixels
high. That's a total of 64,000 separate dots, each of which requires
one bit of memory. So it takes 8,000 bytes of memory to produce a
high-resolution screen display.
C-128 High-Resolution Graphics 199
Text Modes
When the C-128 is in 40-column or 80-column text mode, the charac
ters used to create its text display are stored in memory as binary
data. It takes eight bits of data to create one character. When the
eight bits of data that form a character's image are displayed on the
screen, they are arranged as shown in table 11-3.
Table 11-3
Binary Notation Hexadecimal Notation Appearance
Displaying a
Character on the 00000000 00
00011000 18 XX
Screen
00111100 3C XXXX
01100110 66 XX XX
01100110 66 XX XX
01111110 7E xxxxxx
01100110 66 XX XX
00000000 00
Line 9 Byte 320 Byte 328 Byte 336 ... Byte 632
Line 10 Byte 321 Byte 329 Byte 337 ... Byte 633
Line 11 Byte 322 Byte 330 Byte 338 ... Byte 634
Line 12 Byte 323 Byte 331 Byte 339 ... Byte 635
Line 13 Byte 324 Byte 332 Byte 340 ... Byte 636
Line 14 Byte 325 Byte 333 Byte 341 ... Byte 637
Line 15 Byte 326 Byte 334 Byte 342 ... Byte 638
Line 16 Byte 327 Byte 335 Byte 343 ... Byte 639
.. .and so on.
170 C0L=INT(X/8)
180 R0W=INT(Y/8)
Next, you have to figure out the dot's coordinates inside its 8-by-
8 dot matrix. Here's how the MAKEWAVE.BAS program does it:
200 BYTE=BASE+ROW*320+COL*8+LINE
Finally, you can turn on the bit you have selected with a line
such as this:
216 ($D8 in hex notation). This operation may not sound familiar to
Commodore 64 programmers because it has no equivalent in C-64
programming. But it is of critical importance in high-resolution C-128
programs such as MAKEWAVE.BAS. Here's why.
In the C-128 (but not in the C-64), memory location 216 (or $D8
in hex) is a flag that determines what kind of display the computer
will generate in 40-column mode. Every Veo of a second, the C-128
checks memory address $D8 and immediately goes into the graphics
mode indicated by the flag's setting. And, because the register's de
fault setting is for 40-column text, the C-128 will not stay in high-
resolution graphics mode for more than Yeo of a second unless the
default value of memory location $D8 is changed. The settings of the
flag are shown in table 11-5.
Table 11-5
Decimal Hexadecimal Mode
Setting the $D8
Register 224 $E0 GRAPHIC 4 (split screen, multicolor high
resolution and text)
160 $A0 GRAPHIC 3 (multicolor, high resolution)
96 $60 GRAPHIC 2 (split screen, high resolution and
text)
32 $20 GRAPHIC 1 (high resolution)
0 $00 GRAPHIC 0 (text)
SCROLY Register
The POKE instruction in line 50 is also quite important; but, unlike
the POKE in the previous line, this one is also used in Commodore 64
high-resolution programs. It sets bit 4 of memory address 53265
($D011), an important C-64/C-128 register called the SCROLY regis
ter. In the Commodore 128, and in the Commodore 64, bit 4 of the
SCROLY register is what turns on the computer's bit-mapped 40-
column mode.
Now we have come to line 60 of MAKEWAVE.BAS—and from
that line on, every instruction in the MAKEWAVE.BAS program
would be just as much at home in a Commodore 64 program as it is in
this one. In line 60, a BASIC variable called BASE is defined, and its
value is set at 8192 (or $2000 in hex). This is the starting point of the
high-resolution screen map set up in line 30. In statements 70
through 100, a horizontal line is drawn across the middle of the
screen using a standard, screen-plotting subroutine that extends from
line 170 through line 230. Next, in lines 110 through 140, a sine wave
is drawn on the screen using the screen-plotting subroutine in lines
170 through 230 and the standard BASIC function SIN(X). The pro
gram ends with an infinite loop in line 150.
Listing 11-5 1
PLOTWAVE.S 2 * PLOTWAVE.S
program 3
4 ORG $1300
5
6 HMAX EQU 320
7 BASE EQU $2000
8 *
67 * CHAR=HPSN/8
68 *
69 LDA HPSN
70 STA TEMPA
71 LDA HPSN+1
72 STA TEMPA+1
73 LDX #3
74 DLOOP LSR TEMPA+1
75 ROR TEMPA
76 DEX
77 BNE DLOOP
78 LDA TEMPA
79 STA CHAR
80 *
81 * LINE=VPSN AN!) 7
82
83 LDA VPSN
84 AND #7
85 STA LINE
86 *
87 * BITT=7-(HPSN AND 7)
88 *
89 LDA HPSN
90 AND #7
91 STA BITT
92 SEC
93 LDA #7
94 SBC BITT
95 STA BITT
96 *
127 LDA #8
128 STA MPRL
129 LDA #0
130 STA MPRH
131 LDA CHAR
132 STA MPDL
133 LDA #0
134 STA MPDH
135 JSR MULT16
136 LDA MPRL
137 STA TEMPB
138 LDA MPRH
139 STA TEMPB+1
140 *
141 * ADD LINE
142 *
143 CLC
144 LDA TEMPB
145 ADC LINE
146 STA TEMPB
147 LDA TEMPB+1
148 ADC #0
149 STA TEMPB+1
150 *
151 * TEMPA + TEMPB = BYTE
152 *
153 CLC
154 LDA TEMPA
155 ADC TEMPB
156 STA TEMPB
157 LDA TEMPA+1
158 ADC TEMPB+1
159 STA TEMPB+1
160 *
USR(X) Function
One noteworthy feature of the MAKEWAVE2.BAS program is the
way it uses the USR(X) function, which was introduced in chapter 5.
As you may remember from chapter 5, there are some differences
between the way the USR(X) function is used in Commodore 128
programs and the way it is used in Commodore 64 programs. Before
USR(X) is used in a C-64 BASIC program, the starting address of the
machine language program that it calls must be placed in memory
registers 785 and 786 ($0311 and $0312 in hex notation). In programs
written for the C-128, however, the address of the machine language
program must be placed in memory locations 4633 and 4634 ($1219
and $1220 in hex notation).
In line 30 of the MAKEWAVE2.BAS program, with the help of
the BASIC function DECf'X"), a pair of BASIC variables called HPSN
(for "horizontal position") and VPSN (for "vertical position") are
defined. These variables are set to point to memory addresses $0B02
and $0B04, the addresses where the PLOTWAVE.S program expects
to find its horizontal and vertical screen coordinates when it is told to
plot a dot on the screen.
In line 40 of the MAKEWAVE2.BAS program, a binary program
called PLOTWAVE.O (the object code version of the PLOTWAVE.S
program) is loaded into memory using a standard C-128 technique.
First, a variable called A, which initially holds a value of 0, is
changed to contain the value 1. Next, an IF...THEN statement loads
PLOTWAVE.O into memory. PLOTWAVE.O will not load, however,
C-128 High-Resolution Graphics 209
With the right software, the Commodore 128 can understand many
languages—BASIC, C, Logo, Forth, and dozens more. But over the
years, only one language has been used to create a significant number
of commercial-quality, high-resolution graphics programs. That lan
guage is—wouldn't you know it?—assembly language.
The reason is speed. In previous chapters, we saw how pain
fully slow BASIC can be when it handles a graphics program—partic
ularly a high-resolution graphics program. In this chapter, you'll get a
chance to type and run two graphics routines that are written com
pletely in assembly language and—not surprisingly—run considera
bly faster than the BASIC programs presented in chapters 10 and 11.
one bit right after the other, in 8,000 consecutive bytes of screen
RAM. But the C-128's high-resolution screen is arranged in quite a
different manner; instead of being laid out in consecutive bytes, like
screen RAM, it is split into a grid of 1,000 rectangles, each one 8 bytes
high. This grid measures 40 rectangles wide by 20 rectangles deep—
1,000 cells in all, arranged exactly like the characters on the C-128's
40-column text screen.
Table 12-1 illustrates the relationship between the screen mem
ory of the Commodore 128 and the display that the data produces on
the screen. It shows where the first 32 bytes of screen RAM starting
at memory address $2000 are located when they are displayed on a
high-resolution screen.
Table 12-1
Column 1 Column 2 Column 3 Column 4
How Data Is
Displayed Line 0 $2000 $2008 $2010 $2018
Line 1 $2001 $2009 $2011 $2019
Line 2 $2002 $200A $2012 $201A
Line 3 $2003 $200B $2013 $201B
Line 4 $2004 $200C $2014 $201C
Line 5 $2005 $200D $2015 $201D
Line 6 $2006 $200E $2016 $201E
Line 7 $2007 $200F $2017 $201F
Figure 12-1
ao 319.0
Using X/Y
coordinates
199,0 199,319
ROW = INT(Y/8)
COL = INT(X/8)
LINE = Y AND 7
Another odd quirk about the C-128 screen is that the 8 bits in
each byte of screen RAM are displayed in the opposite order from
how they are stored in memory—with bit 0 on the left and bit 7 on
the right. So we need an equation like the following to get the 8 bits in
each byte of screen RAM into the proper order for a screen display:
BIT = 7 - (X AND 7)
SQUARE.S Program
Now that we know how the C-128's screen map and color map work,
we're ready to take a look at SQUARE.S, shown in listing 12-1. As
mentioned previously, the program contains two separate routines.
One, labeled PLOT, begins at line 97; the other, which draws a
square on the screen, begins at line 258.
Listing 12-1 1 *
4 ORG $1300
5 *
19 *
47 JMP START
48 *
72 MULT16 LDA #0
Advanced C-128 High-Resolution Graphics 217
93 * PLOT ROUTINE
94 *
95 * ROW=VPSN/8
96 *
1 03 * CHAR=HPSN/8
1 04 *
1 05 LDA HPSN
1 06 STA TEMPA
1 07 LDA HPSN+1
1 08 STA TEMPA+1
1 09 LDX #3
1 10 DLOOP LSR TEMPA+1
1 11 ROR TEMPA
1 12 DEX
1 13 BNE DLOOP
1 14 LDA TEMPA
1 15 STA CHAR
1 16 *
1 17 * LINE+VSPN AND 7
1 18 *
1 19 LDA VPSN
1 20 AND #7
1 21 STA LINE
218 Assembly Language Graphics and Sound
177 CLC
178 LDA TEMPB
179 ADC LINE
180 STA TEMPB
181 LDA TEMPB+1
182 ADC #0
183 STA TEMPB+1
184 *
187 CLC
188 LDA TEMPA
189 ADC TEMPB
190 STA TEMPB
191 LDA TEMPA+1
192 ADC TEMPB+1
193 STA TEMPB+1
194 *
221 LDA #0
222 STA $FFOO
223 LDA SCROLY
224 ORA #$20
225 STA SCROLY
226 STA $FF01
227 *
You know by now how fast assembly language is—and now we'll
learn how to make it even faster. We'll reveal some of the secrets that
professional programmers use when they want to write superfast
assembly language programs.
In the past few chapters, we've seen how inadequate BASIC is
as a tool for writing high-resolution graphics programs. In chapter 12,
we translated one high-resolution BASIC program into assembly lan
guage and saw how much faster it ran. But that was just the begin
ning. Now we're going to improve the SQUARE.S program presented
in chapter 12, and make it run even faster. By the time we're fin
ished, we'll have it running at the speed of a commercial-quality
assembly language program.
RECTANGLE.S Program
The program we'll be working with in this chapter, titled RECTAN
GLE.S, is shown in listing 13-1. It's an expanded version of the
SQUARE.S program.
Listing 13-1 1 *
4 ORG $1300
5 *
50 JMP START
51 *
77 MULT16 LDA #0
78 STA PRODL
228 Assembly Language Graphics and Scund
1 14 LDA ROW
1 15 STA MPRL
1 16 LDA #0
1 17 STA MPRH
1 18 LDA #<HMAX
1 19 STA MPDL
1 20 LDA #>HMAX
1 21 STA MPDH
1 22 JSR MULT16
1 23 LDA PRODL
1 24 STA TEMPA
1 25 LDA PRODH
1 26 STA TEMPA+1
1 27 *
Trade Secrets 229
130 CLC
131 LDA #<SCRBAS
132 ADC TEMPA
133 STA PTRL,Y
134 LDA #>SCRBAS
135 ADC TEMPA+1
136 STA PTRH,Y
137 *
138 INY
139 JMP YLOOP
140 *
157 LDA #0
158 STA $FFOO
159 LDA SCROLY
160 ORA #$20
161 STA SCROLY
162 STA $FF01
163 •
166 LDA #0
167 STA FILVAL
168 LDA #<SCRBAS
169 STA TABPTR
170 LDA #>SCRBAS
171 STA TABPTR+1
172 LDA #<SCRLEN
173 STA TABSIZ
174 LDA #>SCRLEN
175 STA TABSIZ+1
176 JSR BLKFIL
230 Assembly Language Graphics and Sound
258 * CHAR=HPSN/8
259 *
271 LDA #0
272 ASL CHAR
273 ROL
274 ASL CHAR
232 Assembly Language Graphics and Sound
282 CLC
283 LDA VPSN
284 AND #7 ;LINE
285 ADC CHAR
286 STA TEMPB
287 LDA TEMPB+1
288 ADC #0
289 STA TEMPB+1
290 *
293 CLC
294 LDY VPSN
295 LDA PTRL.Y
296 ADC TEMPB
297 STA TEMPB
298 LDA PTRH,Y
299 ADC TEMPB+1
300 STA TEMPB+1
301 *
Dot-Plotting Formulas
The first step in converting a dot's screen location into its correspond
ing bit in memory is to divide the dot's vertical coordinate, or Y
coordinate, by 8. The result of this operation is the row number of the
8-byte rectangle in which the dot appears. Then, the dot's horizontal
position, or X coordinate, must also be divided by 8. This gives us the
column number of the 8-byte rectangle in which the dot appears.
Next, the dot's horizontal position within its 8-byte rectangle must be
calculated. We can then use the following formula to bring all of the
previous formulas together and calculate the screen column of the
byte in which the desired dot appears:
Trade Secrets 235
BIT = 7 - (X AND 7)
Y Lookup Table
Now that we know how a dot's position on a screen can be converted
into its corresponding position in RAM, we're ready to see exactly
how the RECTANGLE.S program differs from the SQUARE.S pro
gram. The most important difference is this: Every time the
SQUARE.S program plots a dot, it uses the series of formulas just
presented to calculate the dot's position on the screen. But RECTAN
GLE.S does not perform every one of these calculations every time it
plots a dot; instead, each time RECTANGLE.S has to plot the position
of a dot, it consults a Y lookup table and simply looks up the RAM
starting address of the screen line on which the dot appears. The
program then calculates the dot's horizontal coordinate, or X offset,
and adds it to the Y coordinate address it has found in its Y lookup
table. The result of this calculation is the dot's address in RAM. This
procedure reduces considerably the number of calculations that must
be carried out to plot a dot on a screen and can significantly increase
the operating speed of the program.
The Y lookup table used in the RECTANGLE.S program is set
up in lines 97 through 141. As the table is created, it is stored in a
block of memory that begins at memory address $8000. Actually, two
tables are set up in this section of the program; the low byte of each Y
address is stored in a table that starts at memory address $8000, and
the high byte of each Y address is stored in a second table that begins
at $8100. This may sound like a strange way to set up an address
236 Assembly Language Graphics and Sound
table, but it makes good sense because the same offset used to fetch
the high byte of a Y address can also be used to fetch the low byte.
Now let's take a closer look at how a Y lookup table works.
First, the 8502 Y register is used to create a loop in which the starting
address of each line on the screen is calculated. During the loop, the
number of each horizontal line on the screen is loaded into the accu
mulator, beginning with line 0 and ending with line 199.
In lines 106 through 110, each line number is divided by 8 to
pinpoint the row of 8-byte rectangles in which the dot appears. But
this division is carried out in a streamlined way, not in the slow old-
fashioned way that was used in the SQUARE.S program in chapter
12. Each time a line number is loaded into the accumulator, each bit
of the number is moved three places to the right using three LSR
(logical shift right) instructions. Because the bits in a binary byte
progress from right to left in powers of 2, the easiest way to divide a
bit by 2 is to shift each bit in the byte one place to the right. Shifting
each bit two places to the right is equivalent to dividing the bit by 4, a
three-bit shift to the right is the same as dividing by 8, and so on. So
three shifts to the right are used to divide the contents of the accumu
lator by 8.
In lines 112 through 126, the row number that has just been
calculated is multiplied by 320 using a multiplication subroutine,
which appears in lines 77 through 95. This routine looks similar to
the 16-bit multiplication subroutine in the SQUARE.S program, but a
close comparison will show that it's a few bytes shorter. And every
little bit (or byte) helps when you're trying to speed up a program.
After each row number is multiplied by 320, the product is
added to the starting address of the screen map, and the sum is stored
in the low byte and high byte lookup tables that start at $8000 and
$8100. This procedure continues until both tables are filled in.
After the program creates its Y lookup table, it moves to the
process of drawing a rectangle on the screen—with the help of values
poked in during the execution of the RECTANGLE.BAS program.
When the program has the necessary values, it first calculates the X
offset used to display each dot. The program performs this calcula
tion in much the same way that the SQUARE.S program did. Then, in
lines 293 through 300, it looks up the starting address of each screen
line. Finally, it adds each Y offset address to the appropriate X coordi
nate with the help of indirect (Y register) addressing, and thus deter
mines the location of the byte in which each dot appears.
Another tricky shortcut is used in lines 308 through 310 of the
RECTANGLE.S program. In these lines, the equation:
BIT = 7 - (X AND 7)
is solved by using another table—a very short one that appears in line
52. This formula reverses the order of the bits in a byte before
displaying the byte on the screen. We can speed up solving the
equation by using a table instead of calculations.
Trade Secrets 237
JOYSTICK.S Program
The JOYSTICK.S program, in listing 14-1, is a high-resolution version
of the JOYSTICK.BAS program presented in chapter 11. But it runs
much faster, and it works in a slightly different way. It allows the
user to draw on the screen using a game controller, as in the JOY
STICK.BAS program. But it has a slightly different method of reading
the controller's trigger button. In the JOYSTICK.BAS program, the
trigger button prevents the printing of a dot on the screen. In the
JOYSTICK.S program, pressing the joystick trigger completely erases
the screen display.
Listing 14-1 1 *
4 ORG $1300
5 *
48 JMP START
49 *
71 * 16-BIT MULTIPLICATION
72
73 MULT16 LDA #0
74 STA PRODL
242 Assembly Language Graphics and Sound
104 * CHAR=HPSN/8
105 *
106 LDA HPSN
107 STA TEMP
108 LDA HPSN+1
109 STA TEMP+1
110 LDX #3
111 DLOOP LSR TEMP+1
112 ROR TEMP
113 DEX
114 BNE DLOOP
115 LDA TEMP
116 STA CHAR
117
118 * LINE=VPSN AND 7
119
120 LDA VPSN
121 AND #7
122 STA LINE
123
The Fastest Draw 243
178 CLC
179 LDA BYTE
180 ADC LINE
181 STA BYTE
182 LDA BYTE+1
183 ADC #0
184 STA BYTE+1
185 *
188 CLC
189 LDA TEMP
190 ADC BYTE
191 STA BYTE
192 LDA TEMP+1
193 ADC BYTE+1
194 STA BYTE+1
195 *
229 LDA #0
230 STA FILVAL
231 LDA #<BASE
232 STA TABPTR
233 LDA #>BASE
234 STA TABPTR+1
235 LDA #<SCRLEN
236 STA TABSIZ
237 LDA #>SCRLEN
238 STA TABSIZ+1
239 JSR BLKFIL
240 *
288 TAX
289 BEQ READJS
290 LDA RELADS-1,X
291 STA MODREL+1
292 MODREL BNE *
293 M0DR1
294 *
397 PLA
398 STA HPSN+1
399 PLA
400 STA HPSN
401 RTS
402 *
The segment of code in listing 14-2 shows both the source code
and the object code of a short address modification routine, in addi
tion to the addresses of the memory registers into which the object
250 Assembly Language Graphics and Sound
code is stored. You can get a clearer picture of how the program
works by looking at its object code: the machine language part of the
listing.
Look carefully at the routine's object code, and you'll see that
when the subroutine is first called, the accumulator is loaded with a
value labeled—logically enough—VALUE. As you can see in the ob
ject code listing of line 100, that value is fetched from memory regis
ter $02A7.
In the next three lines of the routine, something quite extraordi
nary happens. As you may be able to recognize by now, the instruc
tions in lines 101 through 103 are a standard set of instructions for
incrementing a 16-bit number. But what number is incremented
here? Well, look again at the object code part of the program, and
you'll see that the value that is incremented is whatever 16-bit value
is stored in memory registers $8041 and $8042. What value is that?
Why, it's the value that follows the LDA mnemonic in line 100.
Now take a very close look at the object code listing of this
routine, and you'll see that the routine has now rewritten itself! The
next time the routine is called, line 100 loads the accumulator not
with the value stored in memory register $02A7 but with that value
plus one—and that value continues to be incremented by one every
time the routine is called!
Address modification is a very powerful programming tech
nique used quite often in high-performance assembly language pro
grams. Routines that use address modification are compact and run
fast, and they do not require the use of page zero memory, which is
always in short supply. So a good knowledge of the principles of
address modification can be of great value to the assembly language
programmer.
table, and you'll see that each value in the table has been defined as
being equal to the address of one specific joystick movement routine,
minus the value of the address of line 292 of the JOYSTICKS pro
gram, which is labeled MODREL (an abbreviation, in a backward
sort of way, for "relative modification.")
Now examine lines 290 through 300, and you'll see how an
assembly language program can rewrite itself using the technique of
relative address modification. In line 290, the beginning of the ad
dress modification routine, the direction switch of the game control
ler has just been read, and the value obtained is stored in the 8502
chip's X register. If the controller's trigger button is currently being
pressed, the screen is cleared and the joystick is read again. But if the
trigger button has not been pressed, the accumulator is loaded with
an 8-bit value that points to a specific address: the address of one of
the joystick movement routines in lines 301 through 342.
Next, examine lines 290 and 291. In line 290, an 8-bit value
pointing to the desired address is loaded into the accumulator. Then,
in line 291, that value is stored in a given memory address. How is
that memory address obtained? It has to be calculated using the
label/offset combination MODREL+1.
Where is MODREL+1? The answer is in line 292:
One of the best features of the Commodore 128 is its ability to synthe
size music and sounds. Despite its user-friendly price, the C-128 has
sound-generating and music-generating capabilities that rival those of
music synthesizers used by professional musicians. In this chapter,
you'll learn how to turn your C-128's keyboard into a music synthe
sizer keyboard that can produce an almost limitless variety of sounds.
The C-128's built-in synthesizer can be programmed in either
BASIC or assembly language. But assembly language is a much better
choice—for many reasons. Here are a few:
• Volume, or loudness
• Frequency, or pitch
• Timbre, or sound quality
• Dynamic range, or the difference in level between the
loudest sound that can be heard and the softest sound that
can be heard during a period of time
SID Chip
In the Commodore 128, there is a special microprocessor that can be
programmed to control the volume, frequency, timbre, and dynamic
C-128 Music and Sound 255
range of sounds. This processor, called the 6581 SID (Sound Interface
Device), gives the Commodore 128 its outstanding sound-synthesiz
ing capabilities.
The SID chip has three separate voices, and each of these voices
can be programmed independently. This means that the C-128 can
play music in three-part harmony—or, if you prefer, you can use one
voice for melody, one for percussion, and one for bass. You can also
use the SID chip to generate noises instead of music, and you can
program each of SID's three voices to produce a different sound. SID
can even be taught to mimic human speech, but that requires sophis
ticated programming.
Shortly, you'll get a chance to see—and hear—some of the things
that SID can do. But first we'll have to learn some basic facts about
the SID chip, such as its location in the C-128's RAM and how it can
be accessed in assembly language programs.
Table 15-2
Address Label Function
Memory Map of
the 6581 SID Chip $D400 FRELOl Voice 1 frequency control (low byte)
Registers $D401 FREHn Voice 1 frequency control (high byte)
y $D402 PWLOl Voice 1 pulse waveform width (low byte)
$D403 PWHI1 Voice 1 pulse waveform width (high nibble)
$D404 VCREG1 Voice 1 control register
$D405 ATDCY1 Voice 1 attack/decay register
$D406 SUREL1 Voice 1 sustain/release control register
$D407 FRELO2 Voice 2 frequency control (low byte)
$D408 FREHI2 Voice 2 frequency control (high byte)
$D409 PWLO2 Voice 2 pulse waveform width (low byte)
$D40A PWHI2 Voice 2 pulse waveform width (high nibble)
$D40B VCREG2 Voice 2 control register
$D40C ATDCY2 Voice 2 attack/decay register
$D40D SUREL2 Voice 2 sustain/release control register
$D40E FRELO3 Voice 3 frequency control (low byte)
$D40F FREHI3 Voice 3 frequency control (high byte)
$D410 PWLO3 Voice 3 pulse waveform width (low byte)
$D411 PWHI3 Voice 3 pulse waveform width (high nibble)
$D412 VCREG3 Voice 3 control register
$D413 ATDCY3 Voice 3 attack/decay register
$D414 SUREL3 Voice 3 sustain/release control register
$D415 CUTLO Filter cutoff frequency (low nibble)
$D416 CUTHI Filter cutoff frequency (high byte)
$D417 RESON Filter resonance control register
$D418 SIGVOL Volume and filter select register
one for voice 3. Later in this chapter, the functions of all of the
registers in the block that extends from $D400 to $D414 are covered
in more detail.
SID's Functions
Let's take an overall look at how the SID chip's registers are used to
program the volume, frequency, timbre, and dynamic range of the
three voices of the Commodore 128.
Volume
For some reason, the designers of the Commodore 128 made it impos
sible to control the volume of the SID chip's three voices individu
ally. Instead, the loudness of the overall sound produced by the SID
register is determined by the value placed in the lower four bits (bits
0 through 3) of memory register $D418. This register is sometimes
known as the SIGVOL register.
To control the volume of all sounds produced by the SID chip,
just place a value ranging from $0 to $F in the lower nibble of the
SIGVOL register. The larger the value of this nibble, the louder the
sound that the SID chip produces. If the value of the nibble is $0, no
sound is generated. In most applications, the volume nibble of the
SIGVOL register is kept at $F, its maximum setting.
C-128 Music and Sound 257
Frequency
The pitch of a musical note is determined by its frequency. Fre
quency is usually measured in hertz (Hz), or cycles per second. The
frequencies that can be produced by the Commodore 128's SID chip
range from 0 Hz (very low) to 4,000 Hz (quite high).
The SID chip synthesizes the frequencies of sounds by carrying
out a rather complex mathematical operation. First, it reads a pair of
8-bit values (one "low" value and one "high" value) in a pair of
frequency control registers. The SID chip has six such registers—two
for each voice—and the addresses of all of them are listed in table 15-
1.
When a pair of frequency control registers are loaded with two
8-bit values, the SID chip combines them into a 16-bit value. It then
divides that 16-bit value by a number derived from a certain fre
quency: specifically, the frequency of a system clock built into the
Commodore 128. Then the SID chip can generate a note of the de
sired frequency.
That's quite an involved series of operations, but you don't have
to worry about how they all work to produce a note of a given
frequency on the Commodore 128. All you have to do is place the
proper values in the proper memory registers, and then set a certain
bit in another register. All of the values you need to play eight octaves
258 Assembly Language Graphics and Sound
Timbre
Timbre, or note quality, can be illustrated with the help of a structure
called a waveform. The SID chip can generate four kinds of waves: a
triangle wave, a sawtooth wave, a pulse wave, and a noise wave. To
understand the concept of waveforms, you need to know a little
about harmonics. So here is a crash course in music theory.
With an electronic instrument, it is possible to generate a tone
that has only one pure frequency. But when a note is played on a
musical instrument, more than one frequency is usually produced. In
addition to a primary frequency, or a fundamental, there is usually a
set of secondary frequencies called harmonics. It is this total har
monic structure that determines the timbre of a sound.
When a tone containing only a fundamental frequency is
viewed on an oscilloscope, the pattern produced on the screen is a
pure sine wave, as shown in figure 15-1. When a flute is played, the
waveform it produces is very close to that of a pure sine wave.
Figure 15-1
Sine waveform
Figure 15-2
Triangle waveform
Figure 15-3
Sawtooth
waveform
Figure 15-4
Pulse waveform
The uses of the remaining bits are discussed later in this chapter.
Meanwhile, these are the bits that must be set to select a waveform:
Filters
Many other kinds of waves can be produced with the help of special
filters. Three such filters—a low-pass filter, a high-pass filter, and a
bandpass filter—are built into the Commodore 128. A low-pass filter
masks out frequencies above a certain cutoff frequency and attenu
ates the low frequencies that pass through. A high-pass filter masks
out frequencies below a certain cutoff frequency and attenuates the
high frequencies that pass through. A bandpass filter cuts off fre
quencies that are outside a range near the center of the frequency
spectrum, and attenuates the midrange frequencies that pass
through.
As explained in the section dealing with volume, SID register
$D418—the register that controls volume—also controls the SID
chip's three sound filters. For the sake of simplicity, the filters built
into the SID chip are not used in the program presented in this
chapter. But you are encouraged to experiment with the filters when
you run the program, because you may want to use these filters in
your own programs.
Dynamic Range
The dynamic range of a note is the difference in volume between its
loudest sound level and its softest sound level in a given period of
time. This period of time can range between the time it takes to play a
single note and the length of a much longer listening experience, such
as a musical performance or a complete musical recording. Dynamic
range can be illustrated in many ways. To illustrate and control the
dynamics of notes produced by the SID chip, engineers who designed
the Commodore used a device called an ADSR envelope, or at
tack/decay/sustain/release envelope. An ADSR envelope illustrates
four distinct stages in the life of a note: four phases that every note
undergoes between the time it starts and the time it fades away.
These four phases—called attack, decay, sustain, and release—are
shown in the ADSR envelope illustrated in figure 15-5.
The addresses of the SID registers used to create ADSR enve
lopes are listed in table 15-2. As you can see by looking at this table,
the SID chip has six registers—two for each voice—that control the
attack, decay, sustain, and release characteristics of notes. Each voice
has one register that controls the attack and decay phases of notes,
and another register that controls the sustain and release phases of
C-128 Music and Sound 261
Figure 15-5
ADSR envelope
notes. Following are brief descriptions of the four note cycles identi
fied at the top of figure 15-5.
both the sustain and release characteristics of notes. The three SID
registers that control the sustain and release phases of notes are
$D406 (for voice 1), $D40D (for voice 2), and $D414 (for voice 3).
The low nibble of each of these registers (bits 0 through 3) sets
the duration of a note's release cycle. Each of these "release" nibbles
can be set to a value ranging from $0 (for 6 milliseconds) to $F (for 24
seconds). The normal settings of the SID chip's release nibble are
somewhere between these two extremes.
The high nibble (bits 4 through 7) of each sustain/release regis
ter controls the sustain cycle of a note. But this nibble is not used to
control the duration of the sustain cycle. Instead, it is used to control
the volume that is maintained throughout the sustain cycle. The
duration of a note's sustain cycle must be controlled with either a
timing loop or some other kind of timer. The value of the sustain
nibble of a sustain/release register can range from $0 (for no volume)
to $F (equal to the note's peak volume).
has finished playing—just clear the gate bit of the appropriate SID
control register. After you clear the gate bit, you can change the
settings of any SID registers. Then you can play another note—or
create another sound—by setting the gate bit again. Or, if you prefer,
you can play the same note or create the same sound over and over,
by repeatedly setting and clearing the gate bit while the other SID
registers remain the same.
you can be sure that no interrupts take place while the vector is being
altered, and that the vector is cleanly and safely changed.
MUSIC Program
In listing 15-2, a program titled MUSIC, the CINV vector is stolen so
that a note-timing loop can be inserted into the CINV vector. This
ensures that the musical notes produced by the program are always
precisely timed. The CINV vector is stolen and altered in lines 57
through 66 of the MUSIC program. In lines 57 through 60, the CINV
address ordinarily pointed to by the C-128's built-in CINV vector is
stored in a pair of memory registers called USERADD and USER-
ADD+1. Next, in line 61, the SEI instruction disables maskable inter
rupts. When that has been accomplished, the address of a user-
written routine (a note-timing loop) is stored in the address of the
CINV vector. Then interrupts are re-enabled with a CLI instruction.
The note-timing routine that this operation adds to the C-128's CINV
vector is labeled WAIT. It appears in lines 113 through 119 of the
MUSIC program.
Listing 15-2 1 *
4 ORG $1300
5 *
25 JMP START
26 *
We'll see how the WAIT routine works later in this chapter. For
now, it's sufficient to remember that the routine ends with the state
ment JMP (USERADD). That statement, the first indirect jump that
we have encountered in this book, ends the user-written timing loop
in the MUSIC program with a jump to the address originally pointed
to by the CINV vector.
268 Assembly Language Graphics and Sound
Figure 15-6
Keyboard Computer Key Q W R T U I 0
G#
arrangement for Musical Note C# D# F# G# A# C# D#
the MUSIC
program
Computer Key
Musical Note
Table 15-3 codes used by both the C-64 and the C-128
C-64
Key Key
Code Key Code Key
0 Insert/Delete 33 I
1 Return 34 J
2 Cursor right 35 0 (zero)
3 F7 36 M
4 Fl 37 K
5 F3 38 O (letter)
6 F5 39 N
7 Cursor down 40 +
8 3 41 P
9 W 42 L
10 A 43 -
11 4 44
12 Z 45 i
13 s 46 @
14 E 47
15 Not used 48 +
16 5 49 *
17 R 50 j
18 D 51 Clear/Home
19 6 52 Not used
20 C 53 =
23 X 56 1
24 7 57 Left arrow
25 Y 58 Not used
26 G 59 2
27 8 60 Spacebar
28 B 61 Not used
29 H 62 Q.
30 U 63 Run/Stop
31 V 64 No key pressed (C-64 only)
32 9
Codes used only by the O•128 (keypad keys and gray keys)
Key
Cole Key Code Key
64 Help 77 6
65 8 78 9
66 5 79 3
67 Tab 80 Not used
68 2 81 0
69 4 82
70 7 83 Cursor up
71 1 84 Cursor down
72 Esc 85 Cursor left
73 + 86 Cursor right
74 -
87 No scroll
75 Line feed 88 No key pressed
76 Enter
270 Assembly Language Graphics and Sound
codes. Codes used by both the C-64 and the C-128 appear in the first
part of the table; codes used by the C-128 only are in the second part
of the table.
starts at line 96. Otherwise, the program keeps looping until a valid
note is typed.
When the PLAY routine begins, the first thing the program does
is check the status of a variable labeled CHAR. This variable deter
mines whether a key has just been pressed or whether it is being held
down. If a key has just been pressed, the CHAR register holds a
different value from the value of the key being pressed. If this is the
case, the program jumps to a routine labeled CONT (for "continue")
and a new note is played. But if a key that has already initiated a note
is still being pressed, the CHAR register holds the same value as the
value of the key being pressed, and a new note does not begin.
Because of a tricky feature of the GETKEY routine, the process
just described will always work, even when the same key is pressed
over and over. In lines 85 and 86 of the GETKEY routine, the value of
CHAR is reset to 0 every time you lift your finger from a key.
Because of this feature, a key that is pressed and held down will
cause a note to sound only once. But if the key is released and then
pressed down again, the note will play again.
During the CONT routine, which extends from line 100 through
line 111, the notes are actually played. An interrupt controlled note
timer (labeled TIMER) is set to a value of 60, which corresponds to a
playing time of one second. The value 64 is stored in the voice 1
control register, clearing that register's gate bit and turning off any
note that may be playing. Then, the high-frequency and low-fre
quency codes that correspond to whatever note is selected are stored
in the appropriate SID registers. Next, the value 65 is stored in the
voice 1 control register, setting that register's gate bit and starting a
new note. Then, an unconditional jump is made back to the GETKEY
routine, so that a new note can be played. Meanwhile, the routine
labeled WAIT—now a part of your computer's hardware interrupt
vector—keeps ticking away, making sure that the ADSR envelope of
every note you play is correctly timed.
After you type, assemble, and execute the MUSIC program, you
may decide that you'd like to make it more complicated. As written,
the program uses only one of the SID chip's voices—but it could
easily be expanded into a program that uses all three. Then, to accom
pany your melody line, you could add harmony, a bass line, or even
the sound of drums.
Because you've also learned how to write graphics programs in
assembly language, you could improve the MUSIC program by add
ing some color graphics, creating something interesting to look at
while the music plays. If you want to tackle a really challenging
programming job, you may be able to expand the MUSIC program
into one that can store and play back melodies that you've typed in
on the keyboard. Then, by mixing assembly language and BASIC, you
may even be able to figure out how to store selections that you've
played and recorded on a disk, so that you can reload them any time
you like and incorporate them into other programs.
Programming Sprites
in Assembly
Language
And more C-128
high-resolution graphics
274 Assembly Language Graphics and Sound
telling the VIC-II which video bank to access to get the data it needs
to generate a screen display.
To direct the VIC-II to the correct chip, all you have to do is set
the two lowest bits in memory register $DD00, often referred to as
CI2PRA. If you've had experience programming the Commodore 64,
you may be familiar with the CI2PRA register. In both the C-64 and
the C-128, this register selects the 16K segment of memory accessed
by the VIC-II chip. And in both computers, the register's two lowest
bits are read and written to using a convention known as "active
low," which means that their values are inverted; they must be set to
address bank 0 and cleared to address bank 3.
Figure 16-1 shows how the C-128's two blocks of RAM can be
divided into four 16K video banks each. Table 16-1 shows how bits 0
and 1 of CI2PRA can direct the VIC-II chip to any desired video bank
within either of the C-128's 64K blocks of RAM.
in each memory
block
$4000 $0000
BankO Bank 2
$3FFF $BFFF
$0000 $8000
Table 16-1
$DD00 Hexadecimal
Selecting a Video Video Bank Address Range Setting Equivalent
Bank Using $DD00
0 $0000 through $3FFF XXXXXX11 $03
Register
1 $4000 through $7FFF XXXXXX10 $02
2 $8000 through $BFFF XXXXXX01 $01
3 $C000 through $FFFF XXXXXX00 $00
accessed by the VIC-II. For example, in the program at the end of this
chapter—an assembly language program titled SPRITE—there are
three large blocks of graphics-related data. There's a high-resolution
screen, a character set that has been copied from ROM into RAM,
and a sprite. Because data from each of these memory blocks appears
on the screen at the same time, the C-128's VIC-II chip has to have
access to all three of them simultaneously. This means that all three
blocks of data have to appear in the same 16K video bank in the same
64K block of memory.
This task would not be difficult if the VIC-II chip was set to
access a free 16K block of RAM when the computer is turned on.
Unfortunately, this is not the case. When the C-128 is turned on, the
VIC-II chip is set to access video bank 0 in RAM block A—and, as
pointed out in chapter 10, that is a very crowded block of RAM. It
contains page zero, the 8502 stack, and a big section of BASIC and
operating system RAM—in all, more than 7K of RAM that is difficult,
if not impossible, to use for storing graphics data.
Fortunately, it is not difficult to move the VIC-IFs access area
out of this crowded memory block and into a segment containing
more free RAM. In the SPRITE program, for example, the CI2PRA
chip directs the VIC-II chip to video bank 1 (memory addresses $4000
through $7000) in RAM block A. In lines 412 through 418, the 8502
chip is instructed to access memory bank 15, where the CI2PRA
($DD00) register resides. Then bits 0 and 1 of the CI2PRA register are
set to access video bank 1. A masking operation is used for this
procedure, as illustrated in listing 16-1.
If you have written programs for the Commodore 64, you may
know that the C-64 has one memory register—often called the
VMCSB register—that serves a double function in high-resolution
programs. VMCSB, situated at memory address $D018, is an 8-bit
register that is designed to be used as two 4-bit registers. The high
nibble tells the VIC-II chip where it can find data that it needs to
generate a screen map. The low nibble directs the VIC-II chip to the
segment of memory that contains character data.
In the Commodore 128, the VMCSB register cannot be accessed
directly from user-written programs. Instead, there are two "shadow
registers," addressable from a user-written program, that can be used
to pass instructions to the VMCSB register. In a program that uses a
40-column text screen, memory register $A2C is a shadow register
that is used to address the VMCSB register. In a high-resolution
graphics program, the VMCSB's shadow register is at memory ad
dress $A2D.
copies the C-128's character set into RAM, and then enlarges each
character to four times its normal size. Also, because each character
is stored in RAM in its original size, the giant characters produced by
the SPRITE program do not require a giant-sized section of memory.
Another noteworthy feature of SPRITE'S character-generating
module is its simplicity. To copy the C-128's character set into RAM,
the program uses an algorithm much like the one used for a similar
purpose in chapter 10. But, as each character is called up to be
displayed on the screen, each dot is copied into screen memory twice,
doubling the character's width. Each scan line in each character is
also displayed twice, doubling the character's height. Result: quadru
ple-sized screen characters, all produced in lines 265 through 315 of
the SPRITE program.
Creating a Sprite
Now that we've seen how to set up color maps and bit maps, and
how to create giant-sized screen characters, we're ready to move to
the topic that this chapter is supposed to be all about: creating and
animating sprites in assembly language.
If you've ever worked with sprites in BASIC, you'll probably be
pleased to learn that it's easier to program sprites using assembly
language than it is to create them using BASIC. That's because sprites
are programmed using many kinds of bit and byte manipulations that
are much easier to manage using binary and hexadecimal numbers
than they are using decimal numbers. By the time you finish this
chapter, you'll see why.
What Is a Sprite?
Sprites, as noted at the beginning of this chapter, are graphics charac
ters that can be created, colored, and animated quite easily, and
moved independently. Using ordinary programming techniques, up
to eight sprites can be displayed on a screen simultaneously. These
eight sprites are usually numbered 0 through 7.
Sprites, like programmable text characters, are made of tiny
dots. And, like programmable characters, they can be created using
standard bit-mapping techniques. But sprites are larger than text
characters; a sprite can be up to 24 horizontal screen dots wide and
up to 21 vertical screen dots high.
A sprite can be displayed in any of the 16 colors that are availa
ble to the VIC-II chip. It is also possible to create multicolored sprites.
We won't be covering multicolored sprites in this chapter, but you
can learn all about them in other books, including the system guide
that comes with your computer. From an assembly language point of
view, there's no difference between a multicolored sprite and any
other sprite; after you've handled one sprite in assembly language,
you've handled them all.
Programming Sprites 281
Figure 16-2
Sprite bit map
Figure 16-3
Sprite byte map
Positioning Sprites
Each of the C-128's eight sprites has two position registers: an X
position register that determines the sprite's horizontal placement on
the screen, and a Y position register that determines the sprite's
vertical position. These registers are abbreviated SPOX through SP7X
and SPOY through SP7Y, respectively. In addition, there is a special
most significant X position register (abbreviated MSIGX) that
designates the horizontal positions of all eight sprites. We need this
register because a sprite can be placed in 512 possible horizontal
screen positions—too many positions for an 8-bit register to keep
track of. If a sprite is placed in a position that can be stored as a value
in an 8-bit register—that is, in a position with a value less than 255—
then the MSIGX register is not used. But if the horizontal position of a
sprite has a value of more than 255, a bit in the MSIGX register is set.
Each bit of the MSIGX register equates to the number of a sprite; bit
0 is used for sprite 0, bit 1 is used for sprite 1, and so on.
There is no MSIGY register because we don't need one. A sprite
can be placed in only 256 vertical positions, so only one 8-bit register
per sprite is needed to handle the vertical positioning of sprites on the
C-128's screen.
Table 16-6
Position Position
Sprite Position Hex Address Register Hex Address Register
Registers
Expanding Sprites
As previously mentioned, a sprite normally measures 24 horizontal
screen dots wide by 21 vertical screen dots high. But by using two
Programming Sprites 285
LDA [pointer) ,Y
another; there are other kernel routines that can place values in other
banks, compare values in different banks, and perform jumps from
one bank to another. Descriptions of these routines, and all of the
other routines in the C-128 kernel, can be found in appendix B.
Listing 16-3 1 *
LAST 8 BYTES OF
16 SPENA EQU $D015
17 SPOCOL EQU $D027
18 SPOX EQU $11D6
19 SPOY EQU $11D7 ; SHADOW ADDRESS
20 MSIGX EQU $11E6 ; DITTO
21 YXPAND EQU $D017 ; THIS ONE, TOO
22 XXPAND EQU $DO1D
23 *
72 JMP START
73 *
192 LDA #8
193 STA MPRL
194 LDA #0
195 STA MPRH
196 LDA CHAR
197 STA MPDL
198 LDA #0
199 STA MPDH
200 JSR MULT16
201 LDA MPRL
202 STA TEMPB
203 LDA MPRH
204 STA TEMPB+1
205 *
218 CLC
219 LDA TEMPA
220 ADC TEMPB
221 STA TEMPB
222 LDA TEMPA+1
223 ADC TEMPB+1
224 STA TEMPB+1
225 *
273 LDX #8
274 *
319 PLA
320 TAY
321 PLA
322 TAX
323 *
370 LDY #o
371 LDX LENPTR+1
372 BEQ MVPART
373 MVPAGE JSR GETDATA
374 INY
375 BNE MVPAGE
376 INC MVSRCE+1
377 INC MVDEST+1
378 DEX
379 BNE MVPAGE
380 MVPART LDX LENPTR
381 BEQ MVEXIT
382 MVLAST JSR GETDATA
383 INY
384 DEX
385 BNE MVLAST
386 MVEXIT RTS
294 Assembly Language Graphics and Sound
402 *
412 LDA $0
413 STA $FFOO ;CI2PRA IS IN
BANK 15
414 LDA CI2PRA
415 AND #$FC ;%11111100
416 ORA #$02
417 STA CI2PRA
418 STA $FF01 ;RETURN TO BANK 0
419
420 * PUT SCREEN MAP AT $6000,
421 * COLOR MAP AT $5C00
422 *
428 LDA #0
429 STA FILVAL
Programming Sprites 295
458 LDA #8 *
459 STA HPSN
460 STA HPTR
461 LDA #0 *
462 STA HPSN+1
463 STA HPTR+1
464 LDA #VMID
465 STA VPSN
466 STA VPTR
467 *
470 LDX #0
471 DISP LDA TEXT,X
472 CMP #0 ;EOF
473 BEQ DONE
474 STA LTTR
475 TXA
476 PHA
477 JSR DRAWCH
478 PLA
296 Assembly Language Graphics and Sound
483 CLC
484 LDA HPTR
485 ADC #16
486 STA HPTR
487 STA HPSN
488 LDA HPTR+1
489 ADC #0
490 STA HPTR+1
491 STA HPSN+1
492 LDA VPTR
493 STA VPSN
494
495 * PRINT NEXT LETTER
496 *
497 INX
498 JMP DISP
499 *
553 LDA #1
554 STA XXPAND
555 STA YXPAND
556 *
559 LDA #1
560 STA SPENA
561 *
Table 17-1
o_,o ^, , , Addresses within 8563 Chip Contents of RAM
oooo Chip s E
Internal RAM $00000 through $07FF Screen display area (screen map)
Layout $0800 through $0FFF Character attributes (colors and
features of text characters)
$2000 through $3FFF Character definitions (character
generator data)
Table 17-2
Register Default Setting Name and Function
8563 Chip's
R0 $7E Horizontal total. The number of characters,
Internal Registers
minus 1, between successive horizontal
sync pulses. Setting is based in part on
monitor specifications.
Rl $50 Horizontal displayed. The number of
characters in each horizontal row.
R2 $66 Horizontal sync position. The number of
characters from the displayed part of a
horizontal row to the start of the horizontal
sync pulse.
R3 $49 Horizontal and vertical sync width. Bits 0
through 3 hold the width of the horizontal
sync pulse in characters, plus 1. Bits 4
through 7 hold the width of the vertical
sync pulse in scan lines.
R4 $27 Vertical total. The number of character
rows, minus 1, between vertical sync
pulses.
R5 $E0 Vertical total adjust. The number of scan
lines added to the end of the display frame
for adjustment of the vertical sync rate.
R6 $19 Vertical displayed. The number of
characters displayed in a frame. This
register sets the height of the frame.
R7 $20 Vertical sync position. The number of
character rows, plus one, from the first
displayed character row to the start of the
vertical sync pulse.
R8 $FC Interlace mode. Used to define the interlace
mode of screen characters; that is, how scan
lines are combined to produce the
characters on the screen.
R9 $E7 Character total (vertical). The number of
scan lines, minus one, used to create each
character on the screen.
RIO $A0 Cursor start scan line and cursor mode. Bits
0 through 4 determine the top scan line
used for display of the cursor. Bits 5
through 6 set the characteristics of the
cursor, as follows:
00 = nonflashing
01 = invisible cursor
10 = fast flashing
11 = normal flashing
Rll $E7 Cursor end scan line. Bits 0 through 4
determine the number of the scan line at
which the bottom line of the cursor will be
displayed. Bits 5 through 7 are always set.
R12 $00 Display start address (high). The high byte
of the start of video RAM in the VDC's
memory.
1. Load the accumulator with the number of the register that you
want to address.
The 80-Column Commodore 305
4. When the VDC has accepted the value passed to it and has set
bit 7 of memory address $D600, you can store whatever value
you like in the VDC register that you have chosen. You must
store the value in memory address $D601. The VDC then en
sures that the desired value is written to the desired register.
1 030 *=$0D00
1 040
1 050 VDC = $D600
1 060 ■
1 030 *=$0D00
1 040 ■
Table 17-3
Bit Attribute
Attributes and Their
Corresponding Bits 7 Alternate character set
6 Reverse video
in 8563 RAM
5 Underline
4 Flashing character
3 Red*
2 Green*
1 Blue*
0 Intensity
"These three color bits and bit 0, the color intensity bit, can be combined to
produce a total of 16 colors. Colors produced by bits 0 through 3 of each attribute
bit—as well as by bits 0 through 3 and bits 4 through 7 of register 26—are illustrated
in table 17-4.
Table 17-4
Bit Combination Color
Colors Produced (in hexadecimal)
by the 8563's Color
0 Black
Attribute Bits Dark gray
1
2 Blue
3 Light blue
4 Green
5 Light green
6 Cyan
7 Light cyan
8 Red
9 Light red
A Purple
B Light purple
C Brown
D Yellow
E Light gray
F White
The 80-Column Commodore 311
attribute table. But, unless you use a small screen for some special
application, you probably won't have much use for this procedure.
When the VDC's attribute table is disabled, the color of each dot
on the screen is controlled by the setting of VDC register 26; bits 0
through 3 of register 26 determine the color of each bit in screen
memory that is turned on, and bits 4 through 7 determine the color of
each bit in screen memory that is off. The colors produced by bits 0
through 3 and bits 4 through 7 of register 26 are listed in tables 17-3
and 17-4.
BYTE = INT(X/8) + Y X 80
BIT = 2t(7 - (X AND 7))
MOUSE80.SRC Program
Our last program in this chapter is MOUSE80.SRC, a program that
sets up an 80-column bit-mapped screen and then allows the C-128
user to draw on the screen with a hand controller, such as a mouse or
a joystick. Along with the techniques of 8563 programming outlined
in this chapter, it also makes use of the screen-drawing techniques
that were used in the JOYSTICK program in a previous chapter. It is
designed to be executed with the help of a short BASIC program titled
MOUSE.BAS, which is shown in listing 17-4.
well worth the time and effort it will take you to type, assemble, and
study it. Have fun!
3280 TAX
3290 BEQ READJS
3300 LDA RELADS-1,X
3310 STA MODREL+1
3320 MODREL BNE *
3330 MODR1
3340
3350 ; ROUTINES TO MOVE JOYSTICK
3360 ■
3680 ; SUBROUTINES TO
3690 ■
Abbreviations
6502/6510/8502 Memory
A Accumulator
X X register
Y Y register
M Memory register
Addressing Modes
A Absolute addressing
AC Accumulator addressing
Z Zero Page addressing
IMM Immediate addressing
IND Indirect addressing
IMP Implicit (Implied) addressing
AX Absolute Indexed,X addressing
AY Absolute Indexed,Y addressing
IX Indexed Indirect addressing
IY Indirect Indexed addressing
R Relative addressing
ZX Zero Page,X addressing
ZY Zero Page,Y addressing
BRK Break.
Halts the execution of a program, much like an interrupt. Also stores
the value of the program counter, plus two, on the hardware stack, in
addition to the contents of the P register (which now has the B flag
set). BRK is often used in debugging, and affects debuggers in various
6502/6510/8502 Instruction Set 325
ways. For more details, see your assembler's and debugger's instruc
tion manuals.
Flag affected: B
Registers affected: None
Addressing mode: IMP
instruction after the JSR instruction that caused the jump to the
subroutine.
Flags affected: None
Registers affected: None
Addressing mode: A
NOP No operation.
Causes the computer to do nothing for one or more cycles. Used in
delay loops and to synchronize the timing of computer operations.
Flags affected: None
Registers affected: None
Addressing mode: IMP
the result of the operation, and the result of the operation is deposited
in the accumulator.
Flags affected: N, Z
Registers affected: A, M
Addressing modes: A, Z, IMM, AX, AY, IX, IY, ZX
Flags affected: N, Z, C
Registers affected: A, M
Addressing modes: AC, A, Z, AX, ZX
The routines listed in this appendix are built into the Commodore
128's ROM and can be easily incorporated into user-written pro
grams. To use a kernel routine, all a program has to do is fulfill the
listed conditions and then do a JSR to the routine's vector address.
The vector addresses in this appendix are pointers to a jump table, or
table of JSR and JMP statements, that appears at memory addresses
$E000 through $FFFF in bank 15. Although the actual addresses of
the kernel routines may change as the C-128 is updated and im
proved, their vector addresses are guaranteed not to change. So the
following routines and vector addresses can be safely used in user-
written programs.
Vector
Name Address Description
ACPTR $FFA5 Input a byte from the serial bus. A single byte
is accepted from the serial bus and returned in
the accumulator. The TALK call should be
used to prepare for this routine. Most
applications should use a higher-level I/O
routine such as GETIN. Register changed: A.
BASIN $FFCF Input a byte from the input channel. A byte is
fetched from the current input device and
returned in the accumulator. The default
device is the keyboard. CHKIN may be used
prior to calling BASIN to change the input
device. On the Commodore 64, this routine
was called CHRIN but had the same call
vector and worked the same way. Register
changed: A.
BOOT CALL* $FF53 Boot a program from disk. Loads and executes
the desired boot sector from an auto-boot disk.
Before calling this routine, load the
accumulator with the ASCII value of the drive
number, and load the X register with the
device number of your disk drive. Then, when
BOOT CALL is called, it will call and execute
the boot sector of an auto-boot disk. If there is
an error, the command UI is set to the disk
drive and the boot operation is aborted.
Registers changed: A, X, Y.
BSOUT $FFD2 Output a byte to the output channel. Writes
the character in the accumulator to the
current output device, usually the screen. The
output device can be changed by calling
CKOUT prior to calling BSOUT. On the
Commodore 64, this call was named CHROUT
but was functionally identical. Register
changed: A.
CHKIN $FFC6 Set channel to input. Establishes an input
channel using the device number in the X
register. Before calling CHKIN, call OPEN and
then load the X register with the logical file
number corresponding to the channel to be
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
Vector
Name Address Description
The codes listed in this appendix are the values that the Commodore
128's operating system uses to print characters on the screen in
response to a PRINT CHR$(X) statement or a BSOUT kernel call. The
Commodore 128 actually has two sets of characters: one called the
graphics/uppercase set and one called the uppercase/lowercase set.
When the computer is turned on, the uppercase/graphics character
set is active, but the computer can be switched to its lower
case/uppercase set with a PRINT CHR$(14) statement or an
equivalent kernel call. To switch back to the uppercase/graphics
character set, you can use a PRINT CHR$(142) statement or an
equivalent kernel call.
The Commodore 128 also uses two other sets of character-re
lated codes: a set of keyboard codes, which can be used to determine
which key on the keyboard is pressed (or if no key is pressed), and a
set of screen codes, which can be placed directly in screen memory to
generate a screen display. The keyboard code set is listed in chapter
15, and the screen code set is listed in appendix D.
$00 0
$01 1
$04 4
$05 5
$06 6
$10 16
$15 21
C-128 Character Codes 345
$16 22
$17 23
$19 25
$1A 26
$21 33 I !
it
$22 34 M
$23 35 # #
$24 36 $ $
$25 37 % %
$27 39
i
$28 40 ( 1
$29 41 ) )
$2A 42
* *
$2B 43 + +
$2C 44
$2D 45 - -
$2E 46
$2F 47 / /
$30 48 0 0
$31 49 1 1
$32 50 2 2
$33 51 3 3
$34 52 4 4
$35 53 5 5
$36 54 6 6
$37 55 7 7
$38 56 8 8
$39 57 9 9
$3A 58 ;
$3B 59
346 Appendix C
$3C 60 <
$3D 61
$3E 62 >
$3F 63 ?
$40 64 @
$41 65 A a
$42 66 B b
$43 67 C c
$44 68 D d
$45 69 E e
$46 70 F f
$47 71 G g
$48 72 H h
$49 73 I i
$4A 74 J j
$4B 75 K k
$4C 76 L 1
$4D 77 M m
$4£ 78 N n
$4F 79 0 o
$50 80 P P
$51 81 Q q
$52 82 R r
$53 83 S s
$54 84 T t
$55 85 U u
$56 86 V V
$57 87 w w
$58 88 X X
$59 89 Y y
$5A 90 Z z
$5B 91 [
$5C 92 £
$5D 93 ]
$60 96
B B
$61 97
C-128 Character Codes 347
$62 98 B
$63 99 C
$64 100 D
$65 101 E
$66 102 F
$67 103 G
$68 104 H
$69 105 I
$6A 106 J
$6B 107 K
$6C 108 L
$6D 109 M
$6E 110 N
$6F 111 O
$70 112 P
$71 113 Q
$72 114 R
$73 115 S
$74 116 T
$75 117 U
$76 118 V
$77 119 w
$78 120 X
$79 121 Y
$7A 122 Z
$7B 123
$7C 124
$7D 125
m
$7E 126
$7F 127
$80 128
$82 130 Underline off (80 column) 80 column off (80 column)
$83 131
$84 132
$85 133 Fl Fl
$86 134 F3 F3
348 Appendix C
$87 135 F5 F5
$88 136 F7 F7
$89 137 F2 F2
$8A 138 F4 F4
$8B 139 F6 F6
$8C 140 F8 F8
$8F 143 Flash off (80 column) Flash off (80 column)
$97 151 Dark gray (40 column) Dark gray (40 column)
$A1 161 D E
$A2 162 H H
$A3 163
□ □
$A4 164 m
$A5 165
□ □
$A6 166
a □
$A7 167
□ D
$A8 168
B □
$A9 169 E
$AA 170
□ n
C-128 Character Codes 349
$C2 194
CD B
$C3 195 e C
$C4 196
B D
$C5 197
□ E
$C6 198
B F
$C7 G
D
199
$C8 200
a H
$C9 201
s I
$CA 202
Q J
$CB 203 K
$CC 204
□ L
$CD 205
S M
$CE 206
0 N
$CF 207
□ O
$D0 208 P
350 Appendix C
$D1 209 Q
$D2 210 □ R
$D4 212 T
$D5 213 U
$D6 214 V
$D7 215 W
$D8 216 X
$D9 217 Y
$DA 218 Z
$DB 219
$DC 220
$DD 221
m
$DE 222
$DF 223
a
$E0 224 Shift-Space Shift-Space
$E1 225 E D
$E2 226 a
$E3 227
□ □
$E4 228
D a
$E5 229
□ □
$E6 230
a □
$E7 231 □ □
$E8 232
B D
$E9 233
E
$EA 234
□ □
$EB 235
m
$EC 236
a a
$ED 237 H H
$EE 238 a E)
$EF 239
u
$F0 240
e Da
$F1 241
$F2 242
$F3 243
$F4 244
o
$F5 245 c
$F6 246
C-128 Character Codes 351
$F7 247 n □
$F8 248 H B
$F9 249
u U
$FA 250
□ 0
$FB 251
c ED
$FC 252
n a
$FD 253
$FE 254
$FF 255
D
Commodore 128
Screen Codes
354 Appendix D
$00 0 @
$01 1 A a
$02 2 B b
$03 3 C c
$04 4 D d
$05 5 E e
$06 6 F f
$07 7 G g
$08 8 H h
$09 9 I i
$0A 10 J j
$0B 11 K k
$0C 12 L 1
$0D 13 M m
$0E 14 N n
$0F 15 O o
$10 16 P P
$11 17 Q q
$12 18 R r
$13 19 s s
$14 20 T t
$15 21 U u
$16 22 V V
$17 23 w w
$18 24 X X
$19 25 Y y
$1A 26 Z z
C-128 Screen Codes 355
$1B 27 [
$1C 28 £
$1D 29 ]
$1E 30 Cursor up Cursor up
$21 33 !
it
$22 34
$23 35 #
$24 36 $
$25 37 %
$26 38 &
$27 39
$28 40 i
$29 41 i
*
$2A 42
$2B 43 +
$2C 44
$2D 45 -
$2E 46 .
$2F 47 /
$30 48 0 0
$31 49 1 1
$32 50 2 2
$33 51 3 3
$34 52 4 4
$35 53 5 5
$36 54 6 6
$37 55 7 7
$38 56 8 8
$39 57 9 9
$3A 58
$3B 59
$3C 60 <
$3D 61
$3E 62 >
$3F 63 ? ?
$40 64
B
356 Appendix D
$41 65 A
$42 66
m B
$43 67 B C
$44 68 D
$45 69 E
$46 70 B F
$47 71
D G
$48 72
o H
$49 73
a I
$4A 74
a J
$4B 75
a K
$4C 76
□ L
$4D 77
s M
$4E 78 N
$4F 79
□ O
$50 80
□ P
$51 81 m Q
$52 82
□ R
$53 83
® S
$54 84
□ T
$55 85
a U
$56 86 V
$57 87 W
$58 88 X
$59 89 Y
$5A 90 Z
$5B 91
$5C 92
$5D 93 m m
$5E 94
$5F 95
$61 97
E E
$62 98
H a
$63 99
□ □
$64 100
□ a
$65 101 D a
$66 102
a
C-128 Screen Codes 357
$67 103
□ □
$68 104
B □
$69 105
E
$6A 106
□ □
$6B 107
m CB
$6C 108 a a
$6D 109 H H
$6E 110
ED H
$6F 111
□ □
$70 112
B C3
$71 113
H a
$72 114
e a
$73 115
si BD
$74 116
D D
$75 117 E C
$76 118
a a
$77 119
n n
$78 120
n
$79 121 u u
$7A 122 □ 0
$7B 123 E B
$7C 124
a a
$7D 125
B B
$7E 126 E E
$7F 127 s fi
E
Commodore 128
Frequency Codes
360 Appendix E
This appendix lists the codes that can be placed in the 6581 SID
chip's low and high frequency registers to produce the following
notes. Instructions for using these codes in Commodore 128 pro
grams are in chapter 15.
Frequency
Octave Pitch High Byte Low Byte
0 C 1 12
C# 1 28
D 1 45
D# 1 62
E 1 81
F 1 102
F# 1 123
G 1 145
G# 1 169
A 1 195
k# 1 221
B 1 250
1 C 2 24
C# 2 56
D 2 90
D# 2 125
E 2 163
F 2 204
F# 2 246
G 3 35
G# 3 83
A 3 134
A# 3 187
B 3 244
2 C 4 48
C# 4 112
D 4 180
D# 4 251
E 5 71
F 5 152
F# 5 237
G 6 71
G# 6 167
A 7 12
A# 7 119
B 7 233
3 C 8 97
C# 8 225
D 9 104
D# 9 247
E 10 143
F 11 48
F# 11 218
G 12 143
G# 13 78
A 14 24
A# 14 239
B 15 210
C-128 Frequency Codes 361
Frequency
Octave Pitch High Byte Low Byte
c 16 195
c# 17 195
D 18 209
D# 19 239
E 21 31
F 22 96
F# 23 181
G 25 30
G# 26 156
A 28 49
A# 29 223
B 31 165
C 33 135
C# 35 134
D 37 162
D# 39 223
E 42 62
F 44 193
F# 47 107
G 50 60
G# 53 57
A 56 99
A# 59 190
B 63 75
C 67 15
C# 71 12
D 75 69
D# 79 191
E 84 125
F 89 131
F# 94 214
G 100 121
G# 106 115
A 112 199
A# 119 124
B 126 151
C 134 30
C# 142 24
D 150 139
D# 159 126
E 168 250
F 179 6
F# 189 172
G 200 243
G# 212 230
A 225 143
A# 238 284
B 253 46
F
Commodore 128
Memory Maps
364 Appendix F
Kernel ROM
^1/0 block
40-column
$E000 - " color
$DCOO " Character
$D800 " ROM I/O block
$D000 -
40/80-column
screen editor
$C000 - RAM storage for
BASIC program
text (starts at
$1000 if 40-column High BASIC
high-resolution
ROM
screen is not used) (or cartridge
RAM storage ROM)
space for
BASIC variables
when BASIC
is in use
$8000 -
Low BASIC
ROM
(or cartridge
ROM)
$4000 -
40-column
high-resolution
screen memory
(or BASIC program
Not
text RAM)
used
BASIC and
kernal RAM
$0800 _
$0400 -
$0000
7
40-column screen memory
Figure F-2
Memory Memory Memory Memory
Four most Bank 0 Bankl Bank 14 Bank 15
$FF00-$FF04
$FFFF •
commonly used $F000 * MMU registers
BASIC BASIC
ROM ROM
(or (or
cartridge cartridge
ROM ROM
$4000 -
Free or
BASIC RAM
(or high-
resolution RAM RAM RAM
screen) from from from
Bank Bank Bank
$1000 -
0 0 0
BASIC and
kernal RAM
$0800 -
$0400 -
$0000 "
Screen map
Page zero and system RAM
366 Appendix F
Figure F-3
RAM Bank 0 RAM Bank 1
C-128'sCP/M $FFFF -
$FFOO- ■ $FF00-FF04
memory map MMU registers
$E000-
BDOS Transient
and BIOS program
banked area
code (TPA)
$2800:
CCP
$1800-
Screen RAM
$1000-
CP/M ROM
$0000- - $OOOO-OOFF
CP/M page zero
C-128 Memory Mops 367
Figure F-4
(Not to Scale)
C-128'sC-64 $FFFF -
Operating
memory map
system ROM
(Commodore kernel)
$DBFF -
Color memory
$D800-
Video, sound, and
I/O RAM and ROM
$D000-
Free RAM
$cooo-
Basic
ROM
$A000-
Free RAM
$0800-
Video
memory
$0400-
Operating system
RAM
$0100-
Page zero RAM-used
by operating system
$0000 -
368 Appendix F
memory map
(ROM image)
$1000-
Free RAM $5000-
$0800-
Video memory
$0400-
Operating system
RAM
$0000- $4000-
Bank 2 Bank 3
$BFFF- $FFFF -
Operating
BASIC ROM
$B000- $F000 - system
or free RAM
RAM
$A000- $E000-
Color memory
$D800-
Character
memory Video and I/O
RAM and ROM
$9000- $D000-
Free RAM
Free RAM (usually)
$8000- $C000 -
Bibliography
370 Bibliography
Absolute addressing, 99-100, 143 ADDNRS.SRC program—cont ASL (arithmetic shift left)
Absolute indexed addressing, 101-3 listings of the, 71, 72, 76, 77, 78, statement—cont
addressing, 99, 147 saving the, 76-77, 81-82 zero page addressing and the, 99
bank switching and the, 179, 185 TSDS assembler and the, 80-82 x addressing and the, 103
CMP statement and the, 116 ADDNRS2.SRC program, 100 ASM command, 68, 81
description of the, 325 label fields and the, 72 mode memory layout, 161-65,
implied addressing and the, 98 Merlin 128 assembler compared 169-73
CLD statement, 64 to the, 69, 71, 72, 73 C64MODE kernel routine, 335
ADDNRS.S program and the, 52, programs of the, 69-70 CURSOR.OBJ program, 311
DMA CALL kernel routine, 336 40-column—cont High-resolution color graphics. See
DOS (Disk Operating System) high-resolution mode Graphics, high-resolution
commands, 79, 81, 89, 90 makeup of, 198, 199-201, 212- color
running machine language 13, 233, 234 HILOADER64 program, 70, 74
programs from, 86, 88-89, sprites and, 274 HITEST.O program, 89, 90
115 text mode, 4 HITEST.S program, 88-89
DOS WEDGE64 program, 70-71, BASIC and, 190-92, 193, 194-
73-74, 77 98, 202
DSAVE command, 79, 81 color border of, 10
I command, 61
D6510 I/O port register, 163-65 makeup of, 198, 199
IF.. .THEN statement, 90
sprites and, 274
Immediate addressing, 98
TSDS assembler and, 81
Implied (implicit) addressing, 98
Frequency codes, C-128, 360-61
E command, 61 INC statement
EDITOR64 program, 69, 73-74, 79 absolute addressing and the, 100
80-column mode, 198, 199 description of the, 327
8563 VDC and, 300-306, 309 Gate, definition of a, 20 zero page
giant characters and, 306-9 G command, 86, 87, 152 addressing and the, 99
8563 Video Display Chip (VDC), GETCFG kernel routine, 336 x addressing and the, 103
335 GET command, 76, 81
INDCMP kernel routine, 336
features of the, 300-301 GETIN kernel routine, 334, 336
Indexed
giant characters and the, 306-9 GOSUB statement, 63, 118
addressing, 103
how it works, 301-4, 309-12 GOTO statement, 63
indirect addressing, 103-4
purpose of the, 300 GRAPHIC command, 168, 202
INDFET kernel routine, 285-86,
using the, 304-6 Graphics, high-resolution color
336
8564 Video Interface Chip II (VIC- BASIC and, 190-92, 193, 194-98,
Indirect indexed addressing, 105,
II), 274-77, 280, 282 201, 203-4, 209, 212, 226,
179
Endless loops, BASIC and, 90 232, 249, 280, 309, 312
INDSTA kernel routine, 336
END statement, 62, 64, 67-68 CI2PRA register, 275-76
Internal registers. See Registers,
.END statement, 73, 75, 80 color memory, 192-93
internal
EOR statement, 143 double, 311-12
Interpreter, BASIC, 10, 13, 42, 66,
absolute addressing and the, 100 giant characters, 279-80, 306-9
79, 162
bit masking and the, 139-40 screen memory, 190-92, 193, 212-
floating-point arithmetic and, 155
description of the, 137, 139, 327 15, 233-37
memory layout and the, 164,
how to use the, 139-40 sprites, 274, 280-85
165, 166, 174, 183
immediate addressing and the, 98 VDC and, 300-312
Interrupt(s)
zero page VIC-II and, 274-77, 280, 282, 312
definition of an, 263
addressing and the, 99 VMCSB register, 276-79
maskable, 49-50, 264, 265
x addressing and the, 103
nonmaskable, 50
sprites and, 274
Heiserman, David L., 166, 176 using, 263-65
Fields, 62-63 HEX function, 29-30 Interrupt disable (I) bit, 49-50, 264
comment, 65, 73 Hexadecimals/hexadecimal number INX statement, 44
label, 63-64, 66, 72, 80 system, 6 description of the, 327
operand, 64, 72-73 advantages of, 27-28 implied addressing and the, 98
operation code, 64, 72-73 ASCII format and, 87 Q.S program and the, 116
Flags, processor status register. See binary compared to, 27-29 INY statement, 44, 98, 327
Processor status register bits C-128 monitor and, 9, 11 IOBASE kernel routine, 336-37
(flags) conversion
IOINIT kernel routine, 335, 337
Floating-point arithmetic, 51-52 from binary, 28-29, 33
BASIC 7.0 interpreter and, 155 from decimal, 20, 28-30, 32-33,
memory layout and, 166 50-51
USR(X) command and, 93 to binary, 21, 28-29, 33 JMPFAR kernel routine, 337
Forth, 212 to decimal, 28-30 JMP statement, 63-64
40-column decimals compared to, 27-29 absolute addressing and the, 100
high-resolution mode, 312 memory banks and, 7, 9 BRANCHIT.S program and the,
BASIC and, 202 summary of the, 20-21, 26 121
giant characters and, 279-80 writing, 26-27 description of the, 118-19, 327
376 Index
listing of the, 194-95 Q.S program and the, 115-16 addressing and the, 99
Joysticks, 194-98, 240 SPRITE program and the, 285 x addressing and the, 103
JOYSTICK.S program zero page
Place value, definition of a, 25 RAM (random-access memory) ROL (rotate left) statement—cont
PLA statement character sets and, 170-71, 172- zero page
description of the, 329 73, 179-84, 300, 309-11, addressing and the, 99
implied addressing and the, 98 335 x addressing and the, 103
stack and the, 111 loaders and, 70 ROM (read-only memory)
PLOT kernel routine, 338-39 monitors and, 70 cartridges, 7, 67
PLOTWAVE.O program, 208-9 page zero of, 98-99 character sets and, 172-73, 179-
PLOTWAVE.S program, 204-8, 209 retrieving 16-bit numbers from, 84, 300, 309-11, 335
PLP statement 38 C-128 kernel and, 88, 89
description of the, 329 ROM compared to, 42-43 C-128 memory banks/blocks and,
implied addressing and the, 98 stack and, 106-11 6-8
stack and the, 111 stack pointer and, 45 RAM compared to, 42-43 See also
POKE command, 37-38, 202, 203 storing 16-bit numbers in, 37-38 Memory layout
POKE16.BAS program, 38 See also Memory layout ROR statement
PRIMM kernel routine, 339 RAMTAS kernel routine, 339 absolute addressing and the, 100
PRINT CHR$(X) statement, 344, R command, 58 accumulator addressing and the,
354 RDTIM kernel routine, 339 99
PRINT command, 193 READSS kernel routine, 339 description of the, 136, 137, 329-
Processor status register (SR), 5, 82, RECTANGLE.BAS program, 232, 30
264 233, 236 ROL statement compared to the,
condition flags of the, 47 RECTANGLE.S program, 233, 249 136, 137
layout of the, 48 listing of the, 226-32 zero page
purpose of the, 45, 47 Y lookup table and the, 235-36 addressing and the, 99
RREG command and the, 92 Registers, internal, 49 x addressing and the, 103
status flags of the, 47 accumulator, 5, 6, 9, 12, 44, 46- RREG command, 92
SYS command and the, 91 47, 67, 82, 91, 92, 93, 105, R6510 I/O port register, 163-65
Processor status register bits (flags) 111, 116,119, 141,147, RS-232 serial port buffers, 66
break, 52-53 179, 185, 334, 335 RTI statement, 98, 330
carry, 48-49, 66-67, 99, 101, 116, 8-bit, 6, 44-45, 47, 48 RTS (return from subroutine)
119, 132-37, 143, 146-48, processor status register, 5, 45, statement, 63, 64, 109
149 47-53, 66-67, 82, 91, 92, ADDNRS program and the, 87
decimal mode, 50-52, 66 264 ADDNRS.S program and the, 67
interrupt disable, 49-50, 264 program counter, 5, 44-45, 65-66, BRANCHIT.S program and the,
negative, 53, 133, 134, 137, 143- 80, 82
120
44 COLORME64.OBJ program and
16-bit, 6, 44-45
overflow, 53, 143-44, 158 the, 10, 13
stack pointer, 5, 45, 82
unused bit, 53 description of the, 330
X, 5, 44, 82, 91, 92, 100, 101-2,
zero, 49, 116, 117, 143 DIV8/16 program and the, 153
103-4, 105, 115-16, 117,
Program counter (PC), 5, 82 implied addressing and the, 98
119, 121, 122, 125, 178,
Merlin 128 assembler/editor JSR statement compared to
179
system, 65-66 the, 118
Y, 5, 44, 82, 91, 92, 93, 100, 101-
purpose of the, 6, 44-45 Q.S program and the, 117
2, 103, 105, 115, 116, 121,
TSDS, 80 RUN command, 73-74, 77, 195
179
Programs. See names of individual
Relative
programs
addressing, 100-101
PRTR command, 68
address modification, 249-52 SAVE kernel routine, 339, 340, 341
Pseudo operation codes, 64, 73, 80
Remark fields. See Comment fields Saville, Winthrop, 155
PUT command, 76, 81
RESTOR kernel routine, 339 SBC statement
RETURN statement, 10, 67 absolute addressing and the, 100
ROL (rotate left) statement description of the, 330
Q command, 68 absolute addressing and the, 100 immediate addressing and the, 98
Q.S program, 128 accumulator addressing and the, zero page
TXS statement, 98, 332 Roger Wagner Publishing, Inc., 56 Y lookup table, 235-36
TYA statement, 98, 332 W command, 58, 68-69 definition of the, 223
Word(s) SQUARE.S program and the, 223
definition of a, 20, 132 Y register (YR), 5, 82, 115, 121
UDTIM kernel routine, 341 16-bit, 80 absolute indexed addressing and
UI command, 334 Wozniak, Stephen, 41 the, 101-2
Unindexed indirect addressing, 105- bank switching and the, 179
6 CPY statement and the, 100, 116
UNLSN kernel routine, 341 X command, 5, 14 indirect indexed addressing and
Unpacking data in memory, 140, Merlin 128 assembler/editor the, 103, 105
142-43 system and the, 58 purpose of the, 44
UNPACKIT program, 142-43 X register (XR), 5, 82, 105, 119, RREG command and the, 92
UNTLK kernel routine, 341 121, 125 SYS command and the, 91
USRDEMO.BAS program, 94 absolute indexed addressing and USR{X) command and the, 93
USRDEMO.SRC program, 94 the, 101-2
USR(X) command, 86 bank switching and the, 178, 179
listings of BASIC programs using BNE statement and the, 122 Zero (Z) bit, 49
the, 94 CPX statement and the, 100, 116, AND statement and the, 143
MAKEWAVE2.BAS program and 122 BIT statement and the, 143
the, 208-9 indexed indirect addressing and CMP statement and the, 116
purpose of the, 92-93 the, 103-4 LOOP statement and the, 117
INX statement and the, 116 Zero page
LDX statement and the, 115 addressing, 98-99, 143
VDC. See 8563 Video Display Chip LOOP statement and the, 117 x addressing, 103
(VDC) purpose of the, 44 y addressing, 103
VECTOR kernel routine, 341-42 RREG command and the, 92 Zilog, 40
VIC-II (8564 Video Interface Chip SYS command and the, 91
II), 274-77, 280, 282, 312
Virtual paging, 184-85
VMCSB register, 276-79
MORE
FROM
SAMS
D Commodore 128® Reference Guide for It contains two general categories of programs-
Programmers David L Heiserman amateur radio technology and general electronics—that
This is the authoritative guide for programmers: from will save time and simplify programming tasks when
the beginner who wants to know about the C128's incorporated into the custom-designed software
power to the advanced programmer who requires programs provided.
specific information about the Commodore 128. Learn ISBN: 0-672-22516-6, $14.95
BASIC as well as assembly language, 40- and
80-column text and graphics programming, and the D Modem Connections Bible
intricacies of the operating system. Carolyn Curtis and Daniel L Majhor, The Waite Group
ISBN: 0-672-22479-8, $19.95 Describes modems, how they work, and how to hook
10 well-known modems to 9 name-brand
microcomputers. A handy Jump Table shows where to
D The Official Book for the Commodore find the connection diagram you need and applies the
128® Personal Computer illustrations to 11 more computers and 7 additional
Mitch Waite, Robert Lafore, and Jerry Voipe, modems. Also features an overview of communications
The Waite Group software, a glossary of communications terms, an
Learn to create detailed graphics and animation and to explanation of the RS-232C interface, and a section on
run thousands of existing Commodore 64 programs. troubleshooting.
Find out how to program in three-voice sound and how ISBN: 0-672-22446-1, $16.95
to use spreadsheets, word processing, the database,
and much more. D Printer Connections Bible
ISBN: 0-672-22466-9, $12.95 Kim G. House and Jeff Marble, The Waite Group
At last, a book that includes extensive diagrams
specifying exact wiring, DIP-switch settings and
□ Commodore 64®/128® Assembly external printer details; a Jump Table of assorted
Language Programming Mark Andrews printer/computer combinations; instructions on how to
This step-by-step guide to programming the make your own cables; and reviews of various printers
Commodore 64, Merlin 64™ and Panther C64™ and how they function.
shows you how to design your own character set, ISBN: 0-672-22406-2, $16.95
write action games, draw high-resolution graphics,
create animated sprite graphics, convert numbers, mix D Computer Connection Mysteries Solved
BASIC and machine language, and program music and Graham Wideman
sound. This book provides the how's and why's of connecting
ISBN: 0-672-22444-5, $15.95 a personal computer to its peripherals for anyone with
a computer system. It provides an introduction to the
machinery available: printers, MIDI, musical interface,
□ Commodore 64® & 128® Programs for Centronics, video hookups, and the RS-232. This quick
Amateur Radio & Electronics Joseph j. carr and easy troubleshooting guide with case studies will
The electronics hobbyist, programmer, engineer, and assist users who deal with a variety of system
technician will enjoy the 23 task-oriented programs for configurations.
amateur radio and 19 electronics programs in this book. ISBN: 0-672-22526-3, $18.95
MORE
FROM
SAMS
Please send me the books whose titles and numbers I have listed below.
Name (please print)
Address
City
State/Zip
Signature
(required for credit card purchases)
Enclosed is a check or money order for $
Include $2.50 postage and handling.
Mail to: Howard W. Sams & Co.
AR, CA, FL, IN, NC, NY, OH, TN, WV residents
Dept. DM
add local sales tax.
4300 West 62nd Street
Charge my: □ VISA □ MC □ AE Indianapolis, IN 46268
Account No. Expiration Date
Commodore 128
Assembly Language
Programming
Easy to read, written by a Commodore owner for Commodore owners,
Commodore 128 Assembly Language Programming will teach you how to write
commercial-quality programs. If you understand even a little BASIC, this text is
for you.
Why assembly language? With machine language (the language taught in most
Commodore 128 books) you are limited to writing short routines for BASIC
programs. To write high-performance Commodore 128 programs, you need the
tool professional program designers use—assembly language.
Commodore 128 Assembly Language Programming:
• Is packed with type-and-run programs and subroutines that you can incorporate
into your own programs
• Deals with input/output operations, 40-column and 80-column graphics, and
music and sound
• Contains tables and diagrams illustrating important features such as architecture
and memory layout
• Covers a host of new features built into the Commodore 128, such as the
machine language monitor, bank switching, 80-column text and graphics, and
new techniques for interfacing between BASIC and assembly language
With this hands-on guide, you can become an expert Commodore 128 assembly
language programmer.
Mark Andrews is the author of the popular Sams book Commodore 64/128 Assembly Language
Programming. He has written seven other computer books, including three about assembly
language. After a stint as consumer electronics columnist with the New York Daily News, he
became a syndicated electronics columnist. He has taught programming and computer science at
the college level, and currently works as a program designer at Pelican Software in Farmington,
Connecticut.