Commodore 128 Machine Language

Jim Butterfield, Associate Editor

This article launches a new series on machine language programming for the Commodore 128. In this installment, we'll examine some basic architectural features of the 128, including memory banking, and look at a program that passes information between BASIC and ML.

The Commodore 128 is truly three computers in one—a Commodore 128 when in 128 mode, a Commodore 64 when in 64 mode, and a Z80-based CP/M computer when in CP/M mode. This series of articles discusses programming the computer in machine language in 128 mode.

When in this mode, the 128's 8502 microprocessor can execute the same instructions as the Commodore 64's 6510 microprocessor, and many of the programming techniques used on the 64 work exactly the same on the 128. These articles are directed especially at programmers who need to make the transition from 64 machine language to 128 ML programming. Of course, if you're familiar with 6502/6510 programming, but the 128 is your first Commodore computer, you can still benefit from the information presented here.

Ground Rules

Here are two simple ground rules to keep you out of trouble on the 128:

First, it's important to stay in bank 15 when writing programs with the computer's built-in machine language monitor (we'll explain what a bank is in a moment). This rule is necessary because of the 128's memory architecture, which can be confusing to a beginner. If you choose a bank number lower than 12, you may end up in a machine configuration which has no Read Only Memory (ROM), making it impossible for your program to call any of the computer's built-in ROM routines.

Second, stay away from areas of Random Access Memory (RAM) which are usually safe on the 64. On the 64, for instance, the cassette buffer located at 828-1019 ($033C-$03FB) is a good place to put short ML programs, and the free RAM block from 49152-53247 ($C00O-$CFFF) is ideal for longer programs. Both areas are unusable on the 128, as you'll quickly learn if you try to put ML code there. The lower area contains critical system vectors and subroutines; if you change their contents, the system will crash. The higher area is covered by Kernal ROM; you can't easily put an ML program there and still have access to ROM routines.

Instead, the 128 has safe areas from 2816-3071 ($0B00-$0BFF) and 4864-7167 ($1300-$1BFF). The first area is the 128's cassette buffer, and the second area is currently unused by the system. In later articles, we'll provide more details on these rules as well as some exceptions to them.

Why Bank 15?

The 128 is capable of seeing its memory as 16 different banks numbered 0-15. The term banks is somewhat misleading, since a bank does not represent a separate 64K block of memory. Instead, each bank represents a different configuration or arrangement of the various available RAM and ROM elements. The bank number determines what the 128 sees within various areas. In some banks, the 128 sees nothing but RAM; in others it sees a combination of RAM and ROM; still other configurations include RAM, ROM, and input/ output (I/O) addresses, and so on.

In fact, there are 256 possible memory configurations. Most of these, however, are of little or no use. For example, though you can configure the computer to see only half of its BASIC ROM and none of its Kernal ROM, it's hard to imagine any use for such an arrangement. Commodore has chosen 16 configurations which seem most useful, named the different configurations banks, and identified them with numbers from 0-15.

Figure 1 shows the configuration for bank 15. From locations $0002-$3FFF there is RAM. The 128 in the computer's name means that the computer has a total of 128K of RAM, which is arranged in two 64K blocks called RAM 0 and RAM 1. Don't confuse these blocks with banks—some RAM from one or both of these blocks appears in every bank, but the amount varies.

The RAM in bank 15 is from RAM 0, the block that holds BASIC program text along with various buffers, vectors, and system variables and subroutines. More about the rest later. For the moment, it's important to notice that a BASIC program's working values—variables, arrays, and strings—are not contained in the same bank as the program text itself.

As shown by Figure 1, most addresses above 16384 ($4000) are seen as ROM. The BASIC interpreter alone occupies a hefty 28K, all the way up to 45055 ($AFFF). Above that, we have the machine language monitor and operating system (Kernal) interspersed with some I/O addresses and a tiny area earmarked for the memory management unit (MMU).

In the I/O section, from 53248-57343 ($D000-$DFFF), all the chips from the Commodore 64 appear in the same addresses. Thus, your favorite 64 POKEs to make sound effects and so forth work exactly the same in 128 mode. There are numerous extra I/O locations to do new jobs, such as controlling the 80-column video chip and reading the extra keys on the 128's keyboard.

At this point, we won't worry about the machinations of the MMU; it's enough to learn that bank 15 provides access to all the I/O chips as well as the Kernal ROM.

When you put a machine language program in RAM 0, you might be tempted to issue a BANK 0 statement from BASIC before you start the program with SYS. After all, bank 0 gives you access to all the memory in RAM 0. Don't do this: It's better to stay in bank 15.

Figure 2 shows the bank 0 configuration. Putting the computer in this configuration will certainly allow it to see your ML program in RAM 0. But the computer can't see its I/O chips or Kernal ROM. The computer has lots of memory, but no way to communicate with the outside world.

What's the lesson? Stay in bank 15. You are limited to 16K of RAM, but that's plenty for most applications. Later in this series, we'll discuss access to other configurations.

If you don't specify a bank, the computer defaults to bank 15. However, it's prudent to execute a BANK 15 statement just before any SYS from BASIC. This ensures that your program will work even if some other program has left the machine configured for a different bank. As a courtesy to other programmers (and users in general), programs that use other configurations should end by returning the machine to the default bank.

Memory Use In RAM 0

Figure 3 illustrates typical memory usage in the first 16K of RAM 0. Note that there are several unused memory areas available for program storage. Unless you're using a graphics mode, BASIC program space begins at 7168 ($1C00). (While programming in ML, you might want to avoid using an otherwise handy program known as the DOS Shell; it moves the start of BASIC up to $5B01 and occupies memory above $1A00—memory you may want to use for your own purposes.)

Figure 3 also reveals other unused or little-used memory zones. If you don't need to use a tape drive, the cassette buffer from 2816-3071 ($0B00-$0BFF) is free. If you aren't using telecommunications, the RS-232 buffers from 3072-3583 ($0C00-$0DFF) are also available. And there's a large block of empty memory marked reserved for applications software that stretches from 4864-7167 ($1300-$1BFF), providing over 2K of contiguous free space.

Friendlier BASIC

BASIC 7.0, the vastly improved BASIC in 128 mode, has several features that simplify the process of combining BASIC and ML. We won't explain all of them in detail, but here is a brief survey. (Your System Guide contains additional information.)

In addition to calling an ML routine, the SYS statement can also pass values from BASIC to ML. The values must be in the range 0-255 and are placed in the microprocessor's registers just before the ML routine takes over. Simply tack them onto the end of the SYS command, separated by commas. Conversely, the RREG command lets you read the processor's registers from BASIC after an ML routine has finished.

The BLOAD command can bring in any ML module (or a graphics screen, etc.) with no fuss or bother. The file loads into the same memory area from which it was saved, and BASIC continues with the next command. This is much simpler than the gyrations required in earlier versions of Commodore BASIC.

BASIC 7.0 also makes it easy to convert numbers between decimal and hexadecimal. The DEC function converts a hexadecimal string into a decimal number. The HEX$ function converts a decimal number into a hexadecimal string.

A Rudimentary Example

The following program isn't particularly useful, but may interest you in the 128's new features. It counts the number of 1 bits in any eight-bit number and prints them out in a table. You may not be excited to learn that the number 14 (binary 00001110) contains three 1 bits, while the number 16 (binary 00010000) contains only one, but the program does demonstrate how to pass information from BASIC to machine language and back again. We'll explain the purpose of each program line as we go. Here's the first one:

100 BANK 15

This statement puts the computer into bank 15, the safest configuration. Since the ML part of our program won't use any Kernal routines or I/O chips, we could use bank 0. But there's no advantage in doing so, and another time we might not be so lucky. Remember, it's always wise to set the bank explicitly rather than assume everyone's computer will be in bank 15.

110 DATA 162,0,74,144,1,232,168,208,249,96

This is the short ML program, stored in the form of DATA statements. It takes a value from the accumulator (A register), counts the 1 bits in the value, and places the result in the X register.

120 FOR J=2816 TO 2825

The actual ML code goes in locations 2816-2825 ($0B00-$0B09), the bottom of the cassette buffer.

130 READ X:T=T+X

140 POKE J,X

150 NEXT J

Before the ML can be used, it has to be READ from the DATA line and POKEd into memory. A simple additive checksum detects most typing errors.

160 IF T<>1334 THEN STOP

If the program stops at line 160, you've made a typing error, most likely in the DATA statements. If not, the ML code is safely planted in memory and we can proceed to the job of bit counting.

200 FOR J=0 TO 20

We're going to count the 1 bits in numbers from 0-20. You can examine higher numbers if you like, but don't try anything over 255.

210 SYS 2816,J

This statement calls the ML program at its starting address of 2816 and passes the value of the variable J to the processor's A register. When the machine language program begins to run, the A register will contain that value. We could also have passed values to the X and Y registers, but this program doesn't require them.

220 RREG S,T

When we reach line 220, the ML program has returned control to BASIC. We'd like to know what values were in the processor's registers, especially the X register, which contained the bit count. The RREG command reads the registers and places their values into BASIC variables. The A register goes into variable S and the X register goes into T. Now T contains the bit count.

230 PRINT J,T

240 NEXT J

That's all it takes. We print the value of J and the bit count T, then go back to do it again.

Yet To Come

We haven't touched yet on the 128's excellent built-in machine language monitor, nor have we explained how to "break the bank"— free ourselves of some of the constraining features of working within banks. Later in this series, we'll do all of this and more.