Assembly language; a coward's introduction. Charles Leedham.
Assembly language programming can accomplish miracles, but it been made to seem almost as mysterious as the Aztec priesthood. It is a fair bet that the majority of personal computer owners are fairly well versed in Basic programming, but very few have ventured into the mysterious realm of assembly language. And more's the pity, as assembly can do things that Basic never dreamed of.
One of the nice things about Basic is that you can see results right away. Type in ?"Hello", press the ENTER key, and the machine nicely prints out HELLO. It is encouraging, and you go on from there. Assembly, on the other hand, has always been presented as a massive task, requiring the mastery of mystical registers, and indirect register addressing, and things that boggle the mind of the lowly Basic programmer. If only you could do something simple with assembly that would show results right away.
That is what this article is all about. You can start off slowly in assembly, and do some nice small things with it. But seeing is believing, and the way to see it is to type into your computer the relatively short Basic program in Listing 1. You will soon see what remarkable things assembly can do in the way of instantaneous screen grpahics. Along the way you can pick up a few reassuring facts about assembly if you want to, but you don't even need to do that. Doing It In Basic
Now, let's pose this problem: you have written a nice little program and you would like to have on the screen the outlines of five playing cards, the values to be put in by the rest of the program. You could draw the card outline with the agonizingly slow set method--you may even have in your cassette collection a commercial program or two that does exactly that.
Or, you may be a step further along, and have worked to graphics via PEEKs and POKEs which enable you to draw things on the screen up to six times faster than you could be setting X and Y. In the TRS-80 card outline program in Listing 1, the figures are drawn about 2-1/2 times faster than they would be using SET.
Take a look at Listing 1. Lines 50 through 100 put the card outlines on the screen using the SET command. This is really quite simple, but it takes what seems like forever as the lines crawl slowly across and down the screen. Then lines 130 through 160 do essentially the same outlines by POKEing graphics symbols directly into the screen locations you have read about in the manuals.
The method is similar to assembly, because by POKEing, you are addressing the 1024 little memory cell on the screen more or less directly. But because you are writing Basic, there is an interpretery in the way to slow things down.
In essence, the interpreter sees the word POKE and stops for a while to look it up in its electronic "Basic to Binary" dictionary, Discovers that you want a certain graphics character to be put into the specified location, and only then does it. It is the looking up that takes over 99% of the time. Actually putting the symbol on the screen once it knows what you want takes something on the order of two microseconds. The Same Thing In Assembly
Now we get to assembly itself, or at least the portion of Listing 1 which accomplishes an assembly (more properly, machine language) presentation of the card outlines. To do this part, I wrote a very small assembly program with an Editor/Assembler utility program. See the source code in Listing 2. The utility program assembled the source code directly into numerical instructions which are the native language of the Z80. Those numerical instructions are the numbers listed in the DATA statements from lines 240 through 290.
How does the Basic program execute machine language instruction? First, the program tells the Z80 that there is a machine language program present in high memory, and specifically that it starts at memory location 32000. That is what line 20 is all about.
Memory locations 16526 and 16527 in the TRS-80 contain the address of still another location at which a machine language program begins, provided you have put the location into those two cells, which is what POKEing 0 into 16526 and 125 into 16527 does. If you want to know more about this process, look at Section 8 of the Level II manual, under USR(x), a section which is somewhat confusingly written, but will tell you more than we have space for here.
The number 32000 in the decimal number system is 7D00H in hexadecimal, but don't let your eyes start glazing over just yet. There will be as little talk about hexadecimal numbers as possible. Just accept the fact that for reasons best known to its designers, the first memory location, 16526, contains the least significant byte of 7D00H, and that byte, the last two figures, is zero.
The second location, 16527, contains the most significant byte, 7DH. Confusing as it is, and despite the fact that the machine works with hexadecimal numbers and binary bits, you must POKE things into the memory locations in the decimal equivalents of the hexadecimal bytes. So the hexadecimal 7DH which must go into 16527 must go in a 125.
Here, by the way, Radio Shack's manual makes a truly staggering error, telling you that to POKE 7DH, you use the figure 208. 208 isn't 7DH; 125 is. Hex To Decimal
As an aside, you convert a two-digit hexadecimal number to decimal by multiplying the first digit by 16 and then adding the second digit, remembering that the letters A through F represent 10 through 15. Seven times 16 is 112 and you add 13 (what D stands for), and you get 125.
Now, to get back on track, line 20 tells the machine that a machine language program begins at location 32000 and that when it later gets an instruction to jump to a machine language routing, that is where to look for the beginning. Line 190 gives that instruction.
Don't be puzzled because I didn't use the recommended ?USR(N) or another common command, ?USR(0). If you do it that way, it will print an N or a 0 in the upper lefthand corner of the screen for reasons which quite escape me. But the version in line 190 doesn't.
The 58 numbers in the DATA lines are direct numerical instructions to the Z80 which the Editor/Assembler provided along with the addresses of the memory locations from 32000 through 32057 in which to put them. The first action line of the assembled program (after line 00100, which tells it where to start) is 00110, and it looks like this on the screen after assembly or printed out on your printer:
7D00 21023D 00110 START LD HL, 15618
This tells you that starting at 7D00 (32000 in decimal), there was a three-byte instruction. Every two digits in the number that foolows represent one byte. So I converte the 21H to 33, the 02H to 2, and the 3DH to 61.
As the first three numbers in the DATA lines, they are POKEd into memory locations 32000 through 32002 by line 30, which is exactly what a pure machine language program would do. From then on, I did the same thing for every memory location and instruction. The DATA numbers were the result, and line 30 reads the DATA numbers one by one and POKEd them one by one into all the memory locations between 32000 and 32057.
Once the computer hits line 190 and see that it is to go to a machine language routine, it immediately looks at locations 16526 and 16527, finds the 32000 address, and jumps there to see what is happening--all in a few microseconds.
Then, at 32000, it finds the number 33. In an instruction location, the number 33 tells the Z80 to load a register pair called HL with the number that follows in the next two memory locations, in this case 32001 and 32002. So it moves swiftly on, finds the overall number 15618, and obediently puts that number into the two memory locations which make up the special section called the HL register pair.
In listing the source code, incidentally, I have used all decimal numbers in the instructions, just to make it easier to look back and forth between the assembly listing and the POKE part of the Basic program, which may give you some clues as to what is going on in the assembly section. Normally, putting decimal numbers in assembly source code instructions s considered bad form, but the machine can handle it nicely.
What all of this has done, essentially, is to tell the Z80 to start with memory location 15618 (a screen location) and do whatever comes next.
What comes next is contained in lines 00120 through 00150 in Listing 2, the last of which has the simple instruction LDIR. If you have done some graphics using POKEs, you know that the upper lefthand corner of the screen is memory location 15360 and the lower right is 16383.
The five assembly lines tell the Z80 to start with location 15618 and fill it with a graphics character--the one represented by TRS-80 graphics code 176--and then do the same with the next one, and so on 60 times, which is what the 60 in the instruction LD BC, 60 means.
The LDIR (along with what is in register pair BC) is a nice automatic loop not unlike FOR X = 1 TO 60. Those five lines, in just 11 bytes, tell the computer to draw a 60-unit horizontal line across the screen, starting at 15618.
The next section tells it to do the same thing starting at 16002, and the remainder tells it how to draw the six vertical lines. As you can see, assembly is an economical way to program, as well as being extremely fast when it comes to graphics.
If you type in the Basic program in Listing 1 and run it, you will see the card outlines drawn first using the SET command, which really takes only a few seconds, but seems quite slow. Next it draws the same lines using POKEs--faster, but still slwo. Then, the outlines are simply popped onto the screen by the assembly section; the card outlines appear on the screen in something under one-thousandth of a second. What Next?
Now, what does this do for you, a non-assembly language programmer? It shows you not only the miracle of machine language graphics, but how they can be made to work quite simply. And that they can be included in a Basic program with just a few DATA statements.
One note of caution: If you type in the Basic program in Listing 1, dump it on a spare bit of tape before you try to run it. If you have made a mistake of so much as a single digit in the DATA statements, you may find yourself with a blank screen, nothing happening, and no way to recover without turning the computer off and starting all over. There are no error messages in machine language. One wrong digit in a machine language instruction can send the program racing off doing all sorts of unintended things, happily loading memory locations with interesting things that aren't meant to be there, including numbers which disable the BREAK key and even the Reset button.
In the DATA statements, the numbers are put into the lines the way they are not just a random, but ot give you an opportunity to play with assembly graphics.
Line 240 contains the numerical instructions equivalent to the first five lines, up to the LDIR, of the assembly program in Listing 2. The last two numbers of Line 230 are the numerical instruction which tell the Z80 to do its LDIR number. The number before that, 176, is the code number for the graphics figure which is the lower two little pixels of the 2 x 3 graphics block. Experimenting With The Program
Once you have the program running and have finished marvelling at the screen machine language graphics, you can begin playing with it a bit. Graphics block code numbers run from 129 through 191, and each one is a little different. Use the editing mode and, for example, put 144 in the third-from-last position instead of 176, and you will get a dotted line along the top of the figure. Or put in 185 and see what happens. Try the same thing with line 250, substituting another graphics code number for 131.
You can even experiment, very carefully, with the 60 in lines 240 and 250. Put in a lower number and the horizontal lines will be shorter. Try a longer number and you will get stripes. But don't try to put a number in larger than 255, the maximum that can be POKEd into that one memory cell. Where To Go From Here
All this fun is well and good, and the idea is to show you that assembly language isn't all that mystical--at least not when you have the instruction numbers sitting there in DATA statements. But what if you get enthusiastic about instantaneous assembly graphics and want something other than a card outline?
You might be lucky enough to have a friend who programs in assembly who will put together a small program for you with his Editor/Assembler and then let you work out the DATA numbers to POKE in.
Or, you might want to get serious about learning assembly. Get an Editor/Assembly, and begin, in easy stages. Type in source code, Listing 2. Find out just how and why it works. Enter a few of the other small and interesting source code listings you have seen in this and other magazines but passed by because they were written in assembly language. Get yourself a good screen location diagram pad (Radio Shack sells them) and experiment with loading different graphics characters into screen memory locations to make an figure you want.
Most important, however, get an Editor/Assembler that allows you to "assemble in memory" so you can write a little experimental program, assemble it directly into memory, switch to Basic, and run the program. If it doesn't work right, you can switch right back to assembler, list the source code on the screen or to your printer, and find out what went wrong. If you can't do "in memory" assembling, it is really tedious to make tapes, dump, re-load, try out, then re-load the assembler.
Microsoft's Editor/Assembler/Plus is a good one but there may be others on the market that do the same work. Don't be afraid of assembly. You may never be an accomplished assembly programmer--but then again you just might. And with even a little knowledge of assembly, you can do some really nice things with your Basic Programs.