Machine Language - Part V
Machine Language - Part V
by Lyle Giese (LYLEG)
This month, I decided to keep my promise to write a program in a label based assembler. The program is a SEQ file reader with the output going to the screen or the printer. The printer device can be selected (4-7) as well as the secondary address (limited to 0-9).
The reason for using ML here should be obvious to anyone who has written a file reader in BASIC. You go to sleep after about the second screen full of information. Speed loaders of course help, but nothing can beat ML here.
I wouldn't expect ANYONE to enter this program without a label based assembler. This program would be just too complicated to do it with a simple assembler. I don't think I could have written it with a simple assembler either.
The assembler I used was PAL. So if you have a copy of the SYSMASS from Transactor it will be compatible with that. And with only minor changes, it would also be compatible with MADS. I have not tried to convert it yet, but these two lines will help:
OPEN8,8,8,"FILENAME,S,W":POKE22,35:CMD8:LIST
PRINT#8:CLOSE8
This will create a SEQ file without the line numbers that MADS can use. PAL relies on the BASIC editor and stores its files as PRG files.
One of the biggest advantages of label based assemblers is the work they take away from you. First you can save your equates and load them as a unit and only type them in once. That way you can type in the equates for the entire KERNAL Jump Table and with PAL save them with the .SST command. Then the next time you want anything from the KERNAL, you would just .LST to load that table in and you have the entire KERNAL Jump Table.
But since this is our first program we have to type in the KERNAL routines we will need to use. And I did that right at the start of the source code. Then I put in the rest of the memory locations I needed and added to it as I needed them in the program.
Also the label based assembler will help to abstract the process. You don't have to keep thinking about all of those numbers and keeping them straight, like the KERNAL routine to read the Status byte after an I/O operation. You don't have to remember each time you call it that it is at $FFB7, you just call it by the name Commodore gave it, READST.
The same goes for branch instructions or for places in the program. Go to line 510. It is the start of the program, and if I want to jump to the start of the program, I just put in a line 'JMP START'. That is a lot easier than remembering 'JMP $C000'.
So let's take a look at the program and see how the little thing works (quite well actually!).
We start out at line 50 by issuing a call to the PAL assembler to start the assembly. Before assembling, you have to load and run PAL which then seals itself off in high BASIC memory and is vectored from the USR function at 700. And then you enter or load your source code and issue the BASIC command run, and PAL takes over.
The next line (75) contains instructions to the assembler. It is telling the assembler what to do with the object code and the printed output. You can send the object directly to memory, disk, cassette, or nowhere. Why would you not want the object code sent some where? If you just wanted to check for syntax errors in the source code, you would run PAL with no object or printed output. PAL can run through this very fast and show just your syntax errors. (No, it won't show your logical errors.)
Another thing you get besides the object code is a printed output. This makes for a nice neat record of your code. It is useful when you have to debug your programs. If you want it generated this output can be sent to disk or printer.
In the equates, I used the Commodore names for the KERNAL routines used. The rest of the equates are variables that I needed as I wrote the program.
Then on to the program itself. The first thing I do is change the screen and border color to black and then the character color to yellow. These are my preferences and can of course be changed to whatever you want.
Then we need to print on the screen the opening message. Again, the label based assembler is very handy at this point. I knew I wanted a message printed here telling you how to use the program and prompting you for the filename. But I just called this 'AMESS' and continued on, knowing that later I would need to write that message and would just call it 'AMESS'.
Since I also realized that I would need to send several messages to the screen, I would save time and trouble and make the screen messaging a subroutine. That way it would need to be written only once. I called it 'MESSOUT'.
Again, to emphasize the point, the label based assembler is handy. I don't need or care what address the subroutine is at. The assembler will take care of that when you assemble the program. It will also make any changes in the program; all calls to that subroutine will be adjusted accordingly.
Now just how do we pass the address of our message to the messout routine? That is easy (at least for me). At the address INDEX and INDEX+1 we store the start address of the message. And we put a zero byte at the end of the message to mark the end. But what do the symbols < and > mean here?
The assembler can do math for you to determine the numbers you want. The < and > symbols get the Low order byte and the HIgh order byte of the address or number AMESS means. In this example, AMESS will end up at $C22B in the finished program. The high order byte is $C2 and the low order byte is $2B.
Just for kicks how do I convert a decimal address into this format? As in my first column the formula is:
Hibyte = int(address/256)
Lobyte = address-(int(address/256)*256)
Isn't it nice that the assembler will do this math for us? (In case you haven't guessed, I am pushing the label based assemblers for any serious work in ML.)
If we look at the message starting at line 5000 (see how we put labels at certain points in the program?), we start by putting a zero in the Y reg and use that as a counter.
In the next instruction, load the first byte of the message into the accumulator using indirect Y addressing. That is the address at INDEX and INDEX+1 will be added to the Y reg and that is where we will get our byte from.
In our example it will be the first byte of the AMESS. That first byte is the character CLEAR/HOME. The next instruction is checking for a zero byte that would signal the end of the message. Then we output the character to the default device which is the screen. Then we increment our counter, the Y reg.
Next we have to check to see if the Y reg rolled over from $FF(255) to $00. Why? Because if we did not then we would pick up the first character of our message and not the 257th character. So if we have already outputed 256 characters, we have to increment the high byte of our index at the address index+1 and then continue picking up and outputing the rest of the message.
When we get to the end (our zero byte) we will branch to the RTS instruction and return to the main part of the program.
The syntax used in making the text strings for the assembler to use is where most assemblers do differ. In my examples PAL uses the puesdo-opt (that is commands for the assembler) .asc and double quotes " to insert the strings. In MADS the puesdo-opt is .byte and a single quote ' followed by the text string. At this point you will have to refer to your manual for the assembler you are using.
It looks like I have covered enough ground for one month. Next month we will explore getting the filename and opening the disk files. Included this month are the parts of the finished source covered here and the full object code of the program. The object code needs to be loaded ,8,1 and then SYS49152 to use it.
Until next month!
Lyle Giese (LYLEG)
mlcol5-s
FILE00008 ==0801==
50 sys700
75 .opt p,oo
80 ;****************
81 ;* *
82 ;* lyle giese *
83 ;* version 1.0 *
84 ;* 06/21/86 *
85 ;* *
86 ;****************
120 readst = $ffb7
130 setlfs = $ffba
140 setnam = $ffbd
150 open = $ffc0
160 close = $ffc3
170 chkin = $ffc6
180 chkout = $ffc9
190 clrchn = $ffcc
200 chrin = $ffcf
210 chrout = $ffd2
220 getin = $ffe4
230 clall = $ffe7
300 index = $fb
310 fnam = $033c;using cassette buffer to store filenames
320 flen = $fd;for length of filename
330 outfile = $fe
340 rtemp = $03ff
350 atemp = $03fe
360 pdev = $03fd
370 shflag = $028d
380 high = $03fc
390 low = $03fb
490 *= $c000
500 ;set up screen
510 start lda #$00;make border and background black
520 sta $d020
530 sta $d021
540 lda #$07;make the characters yellow
550 sta $0286
555 ;send first message to screen
560 lda #<amess
570 sta index
580 lda #>amess
590 sta index+1
600 jsr messout;send message to screen
5000 messout ldy #$00;send message to screen
5010 lpy lda (index),y;index points to start of message
5020 beq end;and it must end with a zero byte
5030 jsr chrout
5040 iny
5050 bne lpy;has y looped around to zero
5060 inc index+1;if it has we must inc
5070 jmp lpy;the hi byte of our index address
5080 end rts
7000 amess =*;opening message
7010 .byte $93;clear/home
7020 .byte $0d,$0d,$0d,$0e
7030 .asc " SEQUENTIAL FILE READER"
7040 .byte $0d,$0d
7050 .asc " By Lyle Giese"
7060 .byte $0d,$0d
7070 .asc " Use the Shift and Shift Lock to pause
7080 .byte $0d
7090 .asc " listing. Press the Ctrl and Shift to"
7100 .byte $0d
7110 .asc " exit current file. To exit just"
7120 .byte $0d
7130 .asc " hit (rvon)return(rvof) at the Filename > prompt."
7140 .byte $0d,$0d
7150 .asc " Filename > "
7160 .byte $00