Assembly Lines by Roger Wagner - Part 16

Assembly Lines, Part 16 was first published in Softalk in January 1982. This was the first article in what was supposed to be "Volume 2" of "Assembly Lines: The Book". Unfortunately, Volume 2 remained unpublished when Softalk went bankrupt in August 1984. Part 16 is actually the first of three parts where Roger Wagner explains how to interface assembly language with Applesoft Basic.

Everyone's Machine Language Guide, Part 16

One useful application of machine language programming is in the enhancement of your existing Applesoft programs. Some people are inclined to write all their programs in machine language, but it may be more efficient on occasion to write “hybrids”—programs that are a combination of Applesoft and machine language. In this way, particular functions can be done by the operating system best suited to the particular task.

If you had to write a short program to store ten names, it would be best to do it in Applesoft:

10 FOR I = 1 TO 10
20 INPUT N$(l)
30 NEXT I

This is much simpler than the equivalent program in machine language. In cases where neither speed nor program size is a concern, Applesoft is a completely acceptable solution.

However, if you had to sort a thousand names, speed would become a concern, and it would be worth considering whether the job could best be done in machine language.

If you have ever done a call in one of your Basic programs, then you have already combined Applesoft with machine code. For example:

10 HOME
20 PRINT "THIS IS A TEST"
30 PRINT "THIS IS STILL A TEST"
40 GET A$
50 VTAB 1: HTAB 5: CALL-958

In this program, a line of text is printed on the screen. After you press a key, all text on the screen after the first word “THIS” is cleared.

Now although it might be possible to accomplish the same effect in Applesoft by printing many blank lines, it would not be as fast or as efficient in terms of code as the call -958.

In executing the above program, the Applesoft interpreter goes along carrying out your instructions until it reaches the call statement. At that point a JSR is done to the address indicated by the call. When the final RTS is encountered, control returns to the Basic program. In between, however, you can do anything you’d like!

Calling routines is hardly complicated enough to warrant an entire article on the subject. The real questions are, how do you pass data back and forth between the two programs, and how can the problem of handling that data be made easier for the machine language program?

Simple Interfacing. The easiest way to pass data to a machine language routine is simply to poke the appropriate values into unused memory locations, and then retrieve them when you get to your machine language routine. To illustrate this, let’s resurrect the tone routine from the May 1981 issue of Softalk.

To use this, assemble the code and place the final object code at $300. Then enter the accompanying Applesoft program.

1 **********************
2 * SOUND ROUTINE #3A *
3 **********************
4 *
5 *
6 OBJ $300
7 ORG $300
8 *
9 PITCH EQU $06
10 DURATION EQU $07
11 SPKR EQU $C030
12 *
13 BEGIN LDX DURATION
14 LOOP LDY PITCH
15 LDA SPKR
16 DELAY DEY
17 BNE DELAY
18 DRTN DEX
19 BNE LOOP
20 EXIT RTS

From the Monitor, this will appear as:

*300L
0300- A6 07 LDX $07
0302- A4 06 LDY $06
0304- AD 30 CO LDA $C030
0307- 88 DEY
0308- DO FD BNE $0307
030A- CA DEX
030B- DO F5 BNE $0302
030D- 60 RTS

This Applesoft program is used to call it:

10 INPUT "PITCH, DURATION? ";P,D 20 POKE 6,P: POKE 7,D 30 CALL 768 40 PRINT 50 GOTO 10

The Applesoft program works by first requesting values for the pitch and duration of the tone from the user. These values are then poked into locations 6 and 7 and the tone routine called. The tone routine uses these values to produce the desired sound, and then returns to the calling program for another round.

This technique works fine for limited applications. Having to poke all the desired parameters into various comers of memory is not flexible, and strings are nearly impossible. There must be an alternative.

The Internal Structure of Applesoft. If you’ve been following this series for long, you’ve no doubt figured out by now that I’m a great believer in using routines already present in the Apple where possible, to accomplish a particular task. Since routines already exist in Applesoft for processing variables directly, why not use them?

To answer this, we must take a brief detour to outline how Applesoft actually “runs” a program.

Consider this simple program:

10 HOME: PRINT "HELLO"
20 END

After you’ve entered this into the computer, typing list should reproduce the listing given here. An interesting question arises: “How does the computer actually store, and then later execute, this program?”

To answer that, we’ll have to go to the Monitor and examine the program data directly.

The first question to answer is, exactly where in the computer is the program stored? This can be found by entering the Monitor and typing in: 67 68 AF B0 and pressing return.

The computer should respond with:

67- 01
68- 08
AF- 18
B0- 08

The first pair of numbers is the pointer for the program beginning, bytes reversed of course. They indicate that the program starts at $801. The second pair is the program end pointer, and they show it ends at $818. Using this information let’s examine the program data by typing in:

801L

You should get:

*801L
0801- 10 08 BPL $080B
0803- 0A ASL
0804- 00 BRK
0805- 97 ???
0806- 3A ???
0807- BA TSX
0808- 22 ???
0809- 48 PHA
080A- 45 4C EOR #$4C
080C- 4C 4F 22 JMP $224F
080F- 00 BRK
0810- 16 08 ASL $08,X
0812- 14 ???
0813- 00 BRK
0814- 80 ???
0815- 00 BRK
0816- 00 BRK
0817- 00 BRK
0818- F9 A2 00 SBC $00A2,Y
08IB- 86 FE STX $FE

This is obviously not directly executable code. Now type in: 801.818

This will give:

0801- 10 08 0A 00 97 3A BA
0808- 22 48 45 4C 4C 4F 22 00
0810- 16 08 14 00 80 00 00 00
0818- 8C

To understand this, let’s break it down one section at a time. When the Apple stores a line of Basic, it encodes each keyword as a single byte token. Thus the world print is stored as a $BA. This does wonders for conserving space. In addition, there is some basic overhead associated with packaging the line, namely a byte to signify the end of the line, and a few bytes at the beginning of each line to hold information related to the length of the line, and also the line number itself.

To be more specific:

0801- 10 08 0A 00 97 3A BA
0808- 22 48 45 4C 4C 4F 22 00
0810- 16 08 14 00 80 00 00 000
818- 8C

The first two bytes of every line of an Applesoft program are an index to the address of the beginning of the next line. At $801,802 we find the address $810 (bytes reversed). This is where line 20 starts. At $810 we find the address $816. This is where the next line would start if there were one. The double 00 at $816 tells Applesoft that this is the end of the Basic listing. It is important to realize that the 00 00 end of the Applesoft program usually, but not always, corresponds to the contents of $AF,B0. It is possible to hide machine language code between the end of the line data and the actual end as indicated by $AF,B0—but more on that later.

The next information within a line is the line number itself:

0801- 10 08 0A 00 97 3A BA
0808- 22 48 45 4C 4C 4F 22 00
0810- 16 08 14 00 80 00 00 00
0818- 8C

The 0A 00 is the two-byte form of the number ten, the line number of the first line of the Applesoft program. Likewise, the 14 00 is the data for the line number twenty. The bytes are again reversed. After these four bytes, we see the actual tokens for each line.

0801- 10 08 0A 00 97 3A BA
0808- 22 48 45 4C 4C 4F 22 00
0810- 16 08 14 00 80 00 00 00
0818- 8C

All bytes with a value of $80 or greater are Applesoft keywords in token form. Bytes less than $80 represent normal ASCII data (letters of the alphabet, for example). Examining the data here we see a $97 followed by $3A. $97 is the token for home, and $3A the colon. Next, $BA is the token for print. This is followed by the quote ($22) and the text for HELLO (48 45 4C 4C 4F) and the closing quote ($22). Last of all, the 00 indicates the end of the line.

In line number twenty, the $80 is the token for end. As before, the line is terminated with a 00.

When a program is executed, the interpreter scans through the data. Each time it encounters a token, such as the print token, it looks up the value in a table to see what action should be taken. In the case of print, this would be to output the characters following the token, namely “HELLO”.

This constant translation is the reason for the use of the term interpreter for Applesoft Basic.

Machine code on the other hand is directly executable by the 6502 microprocessor and hence is much faster, since no table lookups are required.

In Applesoft, a syntax error is generated whenever a series of tokens is encountered that is not consistent with what the interpreter expects to find.

Passing Variables. So, back to the point of all this. The key to passing variables to your own machine language routines is to work with Applesoft in terms of routines already present in the machine. One of the simplest methods was described in the October 1981 issue of Softalk, wherein a given variable is the very first one defined in your program (see the input routine). This is okay, but rather restrictive. A better way is to name the variable you’re dealing with right in the call statement.

The important points here are two components of the Applesoft interpreter: TXTPTR and CHRGET (and related routines).

TXTPTR is the two-byte pointer ($B8, B9) that points to the next token to be analyzed. CHRGET ($B1) is a very short routine that actually resides on the zero page and that reads a given token into the accumulator. In addition to occasionally being called directly, many other routines used CHRGET to process a string of data in an Applesoft program line.

Here then is the revised tone routine :

1 **********************
2 * SOUND ROUTINE #3B *
3 **********************
4 *
5 *
6 OBJ $300
7 ORG $300
8 *
9 PITCH EQU $06
10 DURATION EQU $07
11 SPKR EQU $C030
12 *
13 COMBYTE EQU $E74C
14 *
15 ENTRY JSR COMBYTE
16 STX PITCH
17 JSR COMBYTE
18 STX DURATION
19 *
20 BEGIN LDX DURATION
21 LOOP LDY PITCH
22 LDA SPKR
23 DELAY DEY
24 BNE DELAY
25 DRTN DEX
26 BNE LOOP
27 EXIT RTS

This would list from the Monitor as:

*300L

0300- 20 4C E7 JSR $E74C
0303- 86 06 STX $06
0305- 20 4C E7 JSR $E74C
0308- 86 07 STX $07
030A- A6 07 LDX $07
030C- A4 06 LDY $06
030E- AD 30 CO LDA $C030
0311- 88 DEY
0312- DO FD BNE $0311
0314- CA DEX
0315- DO F5 BNE $030C
0317- 60 RTS

The Applesoft calling program would then be revised to read:

10 INPUT "PITCH DURATION? ",P,D
20 CALL 768,P,D
30 PRINT
40 GOTO 10

This is a much more elegant way of passing the values and also requires no miscellaneous memory locations as such (although for purposes of simplicity the tone routine itself still uses the same zero page locations.)

The secret to the new technique is the use of the routine COMBYTE ($E74C). This is an Applesoft routine which checks for a comma and then returns a value between $00 and $FF (0-255) in the X register.

It is normally used for evaluating pokes, hcolor=t and so forth, but does the job very nicely here. It also leaves TXTPTR pointing to the end of the line (or a colon if there was one) by using CHRGET to advance TXTPR appropriate to the number of characters following each comma. Note also that any legal expression--such as (X-5)/2--can be used to pass the data.

To verify the importance of managing TXTPTR, try putting a simple RTS ($60) at $300. Calling this you will get a SYNTAX ERROR, since upon return, Applesoft’s TXTPTR will be on the first comma, and the phrase “,P,D” is not a legal Applesoft expression.

Now what about two-byte quantities? To do this, a number of other routines are used. For example, this routine will do the equivalent of a two-byte pointer poke. Suppose for instance you wanted to store the bytes for the address $9600 at locations $1000, 1001. Normally in Applesoft you would do it like this:

50 POKE 4096,0: POKE 4097,150

Where 4096 and 4097 are the decimal equivalents of $1000 and $1001 and 0 and 150 are the low-order and high-order bytes for the address $9600 ($96 = 150, $00 = 0).

A more convenient approach might be like this:

50 CALL 768, 4096, 38400

or perhaps:

50 CALL 768, A, V

The routine for this would be:

1 **************************
2 * POINTER SET UP ROUTINE *
3 **************************
4 *
5 *
6 OBJ $300
7 ORG $300
8 *
9 CHKCOM EQU $DEBE
10 FRMNUM EQU $DD67
11 GETADR EQU $E752
12 LINNUM EQU $50 ; ($50,51)
13 *
14 PTR EQU $3C
15 *
16 ENTRY JSR CHKCOM
17 JSR FRMNUM; EVAL FORMULA
18 JSR GETADR ; PUT FAC INTO LINNUM
19 LDA LINNUM
20 STA PTR
21 LDA LINNUM+1
22 STA PTRH-1
23 *
24 JSR CHKCOM
25 JSR FRMNUM
26 JSR GETADR
27 *
28 LDY #$00
29 LDA LINNUM
30 STA (PTR),Y
31 INY
32 LDA LINNUM+1
33 STA (PTR),Y
34 *
35 DONE RTS

which will list from the Monitor as:

*300L

0300- 20 BE DE JSR $DEBE
0303- 20 67 DD JSR $DD67
0306- 20 52 E7 JSR $E752
0309- A5 50 LDA $50
030B- 85 3C STA $3C
030D- A5 51 LDA $51
030F- 85 3D STA $3D
0311- 20 BE DE JSR $DEBE
0314- 20 67 DD JSR $DD67
0317- 20 52 E7 JSR $E752
031 A- AO 00 LDY #$00
031C- A5 50 LDA $50
031E- 91 3C STA ($3C),Y
0320- C8 INY
0321- A5 51 LDA $51
0323- 91 3C STA ($3C),Y
0325- 60 RTS

The special items in this routine include CHKCOM, a syntax-checking routine that serves two purposes. First it verifies that a command follows the call address, and secondly it advances TXTPTR to point to the first byte of the expression immediately following the comma. If a comma is not found, a syntax error is generated.

FRMNUM is a routine that evaluates any expression and puts the real floating-point number result into Applesoft’s floating-point accumulator, usually called FAC. This is a six-byte pseudo register ($97-9C) used to hold the floating-point representation of a number. It includes such nifties as the exponential magnitude of the number and the equivalent of the digits of the logarithm of the number stored.

At this stage you’d have to be something of a masochist to want to deal with the number in its current form, so the next step is used to convert it into a two-byte integer.

GETADR does this by putting the two-byte result into LINNUM, LINNUM+1 ($50,51).

Even if this is not exactly an in-depth explanation of all the most precise details of the operation, the bottom line is that the three JSRs (CHKCOM, FRMNUM, and GETADR) will always end up with the low-order and high-order bytes of whatever expression follows a comma in LINNUM and LINNUM+1.

These simple subroutines should be quite adequate for many applications. Next month, however, we’ll look at string passing, some of the various other routines available, and how to pass data back to the calling Applesoft program.