Optimized Systems Software
A BASIC game is translated into machine language. The comments in the program will teach you how to PLOT, DRAWTO, COLOR, etc., in your own machine language games.
Last month marked the first anniversary of this column in COMPUTE!, and I didn't even notice it. Which tells you how busy I am. We, like almost everyone in the software industry, are beginning to realize that survival comes only to those who diversify. So we are busily introducing new products and concepts. We think the net effect is beneficial to everyone: for us it means a chance to grow and try new approaches; for the user it means newer and better products with a wider choice than ever.
Of course, with the wider choice comes the obvious problem: which one of several competing packages should the user buy? I think I am asked that question only slightly less often than its predecessor: which computer should I buy? I usually sidestep the issue by saying something like this: "Find a software package that seems to do exactly what you want it to do. Ask for references from satisfied customers. When you are convinced that the software will suit your needs, buy the computer that is needed to run the particular software."
The most common problem I see is people buying too little computer for the problem they want to tackle. And, while the problem is sometimes related to the speed of the chosen machine (let's face it, you shouldn't be doing realtime voiceprint analysis with an Atari), the more common problem is simply lack of memory – both kinds of memory, RAM and disk.
This month, I have several topics of interest to Atari aficionados. And, of course, the monster listing of the assembly language version of the "Boing" game (the BASIC version was published last month). Please – hear my disclaimer: I am not nor do I claim to be a game programmer. I am quite aware that Boing is not the epitome of the gamer's art. Rather, I am here attempting to show the fundamentals of writing graphics games in assembly language. So don't type this game in expecting a miracle program; use it for instructional purposes only. Add to it, experiment with it, and chalk it up to experience.
Well, so far we've encountered only one substantial mistake in our book, Inside Atari DOS (published by COMPUTE!). The error occurs in the text on page 11 and in the diagram (Figure 2-3) on page 14. Both correctly indicate the contents of the last three bytes of a data sector (the "link" information), but both assign the wrong order to these bytes. The byte containing the "number of bytes used in sector" is the last byte of the sector (byte 127 in single density sectors), not byte 125 as shown. Then the bytes shown as 126 and 127 move up to become 125 and 126, respectively.
Our apologies for the misinformation; we hope it didn't affect too many of you adversely. I think the mistake came about because of the comment in the listing at line 4312 on page 87, where the file number and sector link bytes are called "bytes 126, 127." Well, they are, if you are numbering from 1 to 128. The tables, etc., in the book are all numbered from 0 to 127; but recall that sectors on the disk are numbered from 1 to 720 (instead of 0 to 719). I don't know why we humans have such a hard time counting from zero, but we do. And computers have a hard time counting from any other number. Oh well.
Incidentally, the only other error in the diagrams that I have found occurs on page 21, where the labels "SABUFH" and "SABUFL" at the heads of the two columns are reversed.
CP/M For Atari?
I often get asked whether OS/A + will run CP/M programs on the Atari (since externally OS/A + looks very, very similar to CP/M—not an accident). But, you simply can't run CP/M on a 6502 (the heart of any Atari or Commodore or Apple). So how do Apple II owners run CP/M? Simple. They plug a card into their machine that essentially disables the 6502 and runs a Z-80 CPU instead. Why not do the same with an Atari?
First, let me say that I don't think that, as a practical matter, it is possible to replace the 6502 in the Atari 400/800 with another CPU (e.g., a Z-80). The reasons are many, but the primary one is the fact that the Atari peripheral chips (particularly Antic) seem somewhat permanently married to the 6502. However, there is no real reason that one could not put a co-processor board in the third slot of an 800 (the co-processor would probably have to have its own memory, though, to avoid interfering with the Atari's DMA and interrupt processing). This is essentially how some manufacturers have added 8086 capability to Apple IIís. But it is expensive, since we now must pay not only for a CPU but also for 65K bytes of RAM and some sort of I/O to talk to the "main" 6502 CPU.
But doing this leaves you stuck with using the Atari serial bus to get data on and off a disk. And, aside from the slow speed, in my opinion an Atari 810 is really too small for practical CP/M work. So, what's the solution, if any? Actually, I've heard of a couple and know of one that is now working.
The first CP/M solution is to simply treat the Atari as an intelligent terminal and hook it up to a CP/M system. While this sounds like overkill, remember that most CP/M systems do not come with a terminal (screen and keyboard), and none can offer the color graphics capabilities of the Atari. But Vincent Cate (alias USS Enterprises) of San Jose, California, has come out with a hardware/ software package that does more than make an Atari into an intelligent terminal. His package also allows most CP/M based computers with a 19,200 baud serial port to effectively replace the disk(s) and printer of an Atari computer.
The CP/M system is turned on and started up first, and it fools the Atari into believing that it is an 810 disk drive (just as does the 850 Interface Module in diskless systems). It thus boots a mini-pseudo-DOS into the Atari which simply passes file requests over the serial bus to the CP/M system. A great idea for someone who has a CP/M system and wants either to get a graphics terminal or to justify buying a game machine.
The primary limitation of this system is simply that you won't be able to read or write Atari-formatted diskettes, though it may be possible to CLOAD from an Atari cassette and then SAVE to the CP/M disk. You won't be compatible with the rest of the Atari world, but for games you probably don't care. At $150, this is the cheapest CP/M to Atari connection, but it does presume the prior purchase of a CP/M-based system.
L. E. Systems (alias David and Sandy Small, et al.) has another method of doing co-processing: remove the cover of your 800 and replace it and the OS ROM board with an extension of the Atari's internal computer bus. On this bus one can stick more memory cards, disk controllers, and (of course) a Z80 card with its own 65K of memory. If your goal is to build a super powerful graphics machine, with access to the vast CP/M library, this is a workable approach (about $1900 with two disk drives, plus the cost of the Atari 800).
However, for about the same money, you could buy a real CP/M machine (such as the Cromemco C-10) with 80-column screen, full function keyboard, built-in printer interface, bigger disks, etc. And then, if you wished, you could hook up your Atari via Vincent Cate's interface. The L. E. Systems' approach, though, assures lightning fast data and control flow between the Z80 and the 6502. More importantly, it allows you to continue to buy and use Atari-compatible disk-based software.
Finally, my rumor mill says that by the time you read this there will be a product available which will function as a more or less conventional Atari-compatible disk controller (à la Percom). But, at the flip of a switch, it will instead boot up and run CP/M (internal to the controller box), treating the Atari as an intelligent terminal, much as Vincent Cate's system does with more conventional CP/M computers.
Do I have any recommendations? Not really. Personally, I like my 128K Byte Cromemco (with 10 Megabyte hard disk and dual 1 Megabyte floppies) for serious software development. But when I think about it, I realize that the thing that makes this system so nice is not the CP/M compatibility (I almost never use CP/M, preferring to stick with Cromemco's Cromix). Rather, it is simply nice to have all that disk space available on command. So why get CP/M? Because you want to get into exotic compiler languages or because you need some very sophisticated business packages. Fine. But for games? Home finances? Learning how to program in BASIC? Graphics? I suggest you avoid CP/M.
Going With Boing
At last, we have here the complete listing of Boing as written in assembly language. As much as practicable, I have done a direct one-for-one translation from BASIC to machine code, without taking advantage of most of the foibles of the machine. Perhaps the only major change I have introduced is also the most unnoticeable from a casual reading of the source: I have made all the variables (which are six-byte floating point numbers in BASIC) into single bytes. This is not always possible. Sometimes, when writing in assembler, one needs numbers greater than 255; then one "simply" uses two-byte integers (or three or four-byte integers, or floating point even).
Except that, on a 6502, that "simply" isn't so simple. There are no 16-bit (or larger) instructions on a 6502, and one must simulate them using series of eight-bit loads, adds, stores, etc. For example, if this program were using Mode 8 graphics, where the horizontal position can vary from 0 to 319 (thus requiring a two-byte number to hold it), all of the code involving the "X…" variables would be larger and more complex. Lesson to be learned: use byte-size numbers whenever possible on a 6502.
Anyway, with regard to the listing of Boing, please note that I didn't leave enough space between my BASIC line numbers to allow my assembly language to share the numbering scheme. So I have put the BASIC lines into the listing in a way that makes them stand out for ease of reading. Presuming that you have read my August and September columns, you will recognize the style and conversions that I have done. Statements such as PLOT, DRAWTO, COLOR, and others have been translated into JSRs to routines in my graphics package. (Note that the listing of the package has been omitted for space considerations. Simply include lines 9000 through 9999 of the listing in my August article.) I would, however, like to discuss a few points of interest.
Notice the coding of lines 2600 and 2700, where the BASIC program had used PTRIG(x)-PTRIG(x + 1) to obtain a + 1, 0, or -1 value from the joystick. But that requires turning the joystick 90 degrees from normal to play the game. As long as we are coding in assembly language, let's do it right!
What we have here, then, is essentially the code that BASIC A + uses for its HSTICK(n) function. I think the code is easy to follow if you remember that the switches in the joystick force a zero bit in locations STICKn when they are pushed. By masking to only the bits we want, and by then inverting the bits, we are able to treat an "on" bit in a more or less normal fashion.
By the way, note that here, as elsewhere in the code, we are also using one-byte numbers to hold both positive and negative values. This works only so long as the absolute value of the signed numbers does not exceed 127, so be careful when using this technique.
Note the simulation of the array YP(n). First, look at how easy it is to handle array elements with constant subscripts, as in BASIC line 1010 (listing lines 1210 to 1230). Even variable subscripts aren't too hard when the array is byte sized and byte dimensioned. Look at BASIC line 4210 (listing lines 6030 and 6040). Admittedly, a true assembly language simulation of the BASIC line would probably go more like this:
LDX HITP LDA SCORE,X CLC ADC #1 LDX HITP STA SCORE,X ;SCORE (HITP) = SCORE(HITP) + 1
But why not be a little smart when making conversions? Besides, if we were writing in some higher level languages, we could have written "INCREMENT SCORE(HITP)".
Finally, the hardest part of this conversion needs some analysis. As we noted last month, in order to provide better movement and bounce characteristics for the ball, we allowed it to have movements (and positions!) of -1, -0.5, 0, +0.5, and + 1. But now we're in assembly language using byte integers. How do we implement fractional movements? We can't really, so we must choose an equivalent scheme.
Notice the variables in the program called "Q.Yxxx". These variables all are used to hold values that represent half movements or positions. Example: if Q.YNEW contains 17, that means it is really representing position 8.5! Notice, then, that before plotting any point that is represented in this fashion, we must divide its value by 2 (by using a LSR instruction, c.f., listing lines 3820, 3930, etc.). Choosing this scheme has some interesting consequences: the last statement of BASIC line 3080 (listing lines 4500 through 4650) is, in some ways, the hardest part of this listing to understand, simply because of the implied "mixed-mode" arithmetic that is used. But it works!
Foibles Of The Assembler/Editor
Writing this article caused me to rediscover some of the foibles of the Atari Assembler/Editor cartridge (and EASMD, for that matter). For many of you, these quirks may seem normal, especially if you haven't used several different assemblers on various machines. But, to others, these eccentricities can be annoying or puzzling.
First, beware of the "* =" pseudo-operator. It is not an origin operator ("ORG" in many assemblers), even though it is used as such! Any label associated with this pseudo-op will take on the value of the instruction counter before the operator is executed. This is necessary since "* = " is also used to reserve storage ("DS" or "RMB" in some assemblers).
Examples: LABELl * = * + 5 ; reserves five bytes of storage ; and assigns the label "LABEL 1" ; to the five bytes * = $4000 ; sets the instruction counter ; to 4000 hex LABEL2 * = $5000 ; assuming this line followed one ; above, assigns 4000 hex to ; "LABEL2" and sets instruction ; counter to 5000 hex!
Second, examine any references to location "CLOCK.LSB" in the Boing listing (e.g., line 5870). Notice that, even though CLOCK.LSB is in zero page, the assembler produced a three-byte instruction for all references to it. This is because the definition of CLOCK.LSB did not occur until after the first reference to it! Actually, the assembler/editor is being remarkably clever here. Remember that the cartridge is, like most assemblers, a two-pass program. It reads the source once to determine where things are and will be, and then it reads the source again to produce the listing and code. But, during the first pass through the source, it can't possibly know whether CLOCK.LSB is in zero page or not, so it chooses the safe route and assumes non-zero page. Then, lo and behold, it discovers that we really wanted the label to be in zero page. What to do?
If we now assign that label to zero page, the second pass of the assembler will produce only two bytes of code here, and all references to labels past that point will be off by one byte. We will have the infamous "phase error." So the assembler has a rule that states "once non-zero page, always non≠zero page," and it continues to generate three-byte references. For a simple assembler like the Atari cartridge, this is a big step. It is still possible to produce phase errors with the cartridge, but it is more difficult than with many 6502 assemblers.
Third and last, there is a problem with the assembler/editor when it comes to multiple forward references. Consider the following code fragment:
AAA = BBB BBB = CCC CCC = 5
There is no way for a two-pass assembler to determine what the value of AAA is! On the first pass, it says "AAA is undefined, because BBB hasn't been defined yet." And then it thinks "BBB is undefined, similarly because of CCC." On the second pass, it should say "ERROR!!AAA is undefined, because BBB still hasn't been defined yet." But it can then produce "BBB is equal to 5 because that's what CCC is equal to."
Unfortunately, the assembler/editor doesn't keep a separate flag meaning "label as yet undefined." The "BBB = CCC" line is sufficient, from the assembler's viewpoint, to establish the existence of "BBB." So, on the second pass, it blindly puts the value of BBB (presumably zero) into AAA. Watch out for this trap! It has snared many a good programmer! I hope you realize that there would be no problems if you had coded that sequence in this order:
CCC = 5 BBB = CCC AAA = BBB
That's it for this month. Next month we will investigate the many languages available to the Atari programmer. We will discuss and fix the major bug in Atari's 850 interface handler (the "Rn:" drivers). And maybe, just maybe, we will try to add cassette tape verification to BASIC.