Using The Assembler Language 04

By Roy Warner

Originally published in EUG #06

LOAD (or type in) the program listed below, which appeared as Fig 1 in Part 3):

       10MODE6:HIMEM=&5800
       20base=HIMEM+1
      190FOR pass=0 TO 3 STEP 3
      200P%=base:[ OPT pass
      240.go:LDX#0
      250.loop:LDAdata,X:JSR&FFEE
      260INX:CMP#0:BNEloop
      270RTS
      320.data
      330EQUS"my string"
      340EQUB13
      350EQUB10
      360EQUB0
      640]:NEXT
      650CALLgo

When working with assembler, structure is a problem as programs tend to be long and repetitive. Most publications set out assembler source code in single statement lines, which may be an advantage. In our listing, a combination of lines 250 and 260 would put a complete operation on one line. Some people find this easier to follow.

Having developed an efficient, effective routine, it is wise to re-use the code as it stands or with slight modification. Descriptive labels help in this respect, for instance in out program ".loop" could be replaced by ".prtlp". By looking for this label the loop may be found in a long listing, then extracted by the sequence *SPOOL file (RETURN), LIST line,line (RETURN), *SPOOL (RETURN). The resultant file can then be *EXEC'd into a new program.

Faced with searching a three or four page listing of multi-statement assembler lines, just to find a short piece of code, Acorn BASIC will, if structured by procedures, become a very attractive language.

Assembler listings can be written within BASIC procedures and assembled into continuous object or machine code - but there are pitfalls to look out for. In our listing, P% is a major problem, as are constants like "base". A further difficulty is that the source code will be assembled by procedure.

Labels are variables within which a hex address is stored. In a two-pass operation, the first pass ignores errors and the labels are assigned values. On the second pass errors are not ignored, but as the labels have now been assigned correct values, errors do not occur. But, if the object code is assembled in a series of two-pass operations, then the code within the first two-pass loop can not read the correct values of labels (variables) in any following loops.

High level languages provide a means of coping with this problem. Acorn BASIC is very tolerant, but takes too many liberties and the "No such variable" rebuke results. Assembler is not tolerant, but a method known as "vectors" is available. A vector is an address followed by a single JMP <address>. In our listing JSR &FFEE is a call to an Operating System vector. Another example of this method can be found in Slogger's Elkman ROM, where an examination of &FFEE to FFEE will reveal a series of JMP <xxxx> commands.

Providing a program is small and simple, the problems are solvable without the complication of using a vector. First, create a PROCconstants, by typing:-

      100DEFPROCconstants                 , then
      110base%=HIMEM+1                    , followed by
      150ENDPROC

Declaring base% as an integer variable is good practice as they use less memory. Change line 20 to:-

       20PROCconstants                    , then add the line
       90END

Next, create a PROCprt by typing:

       30PROCdata
       50CALLgo
      180DEFPROCprt

And change line 200 from base to base% followed by:

      280]:NEXT:base%=P%+1                , and add
      290ENDPROC

PROCprt now needs to be called, so type:

       40PROCprt                          , and add the following lines:
      300DEFPROCdata
      310FOR pass=0 TO 3 STEP 3
      315P%=base%:[OPT pass
      650base%=P%+1
      660ENDPROC

Insert a colon between each procedure, then SAVE the program and RUN it. Note that the code still assembles at the same addresses.

The source code is now much easier to understand. Line 30 has changed the sequence by assembling PROCdata first, so allowing prt to find the label ".data". Not very exciting but it provides an excellent demonstration.

Now RENUMBER 1000,10 then set up your tape or disk system to save files. Type *SPOOL prt (RETURN), LIST 1110,1190 (RETURN). When listed, type *SPOOL again to close the file. Do the same to data, naming the file "data". If using tape, then rewind, then RENUMBER 10,1 and type *EXEC prt. Do this three times, not forgetting to RENUMBER! We need four copies of PROCprt.

Delete the whole of PROCdata and RENUMBER 10,1. Now *EXEC data. RENUMBER 10,10 and insert colons between the PROCs.

Change the three additional PROCprts to PROCprta, b and c; at the same time, make the labels ".go" into .go1, .go2, go3. SAVE and then RUN the program. Type CALLgo1 and the computer responds "No such variable". Insert PROCprta, b and c at lines 41 to 43, SAVE and RUN.

Type CLS:CALLgo:CALLgo1:CALLgo2:CALLgo3 and press RETURN. Four prints of "my string" proves that each copy of the print routine is accessing data.

Edit line 270 to CMP#10 instead of CMP#0. RUN and CALLgo1. "my string" appears. LIST PROCdata and swap over lines 540 and 550. RUN and CALLgo1. Note the position of the prompt. Now restore lines 540 and 550 to their original state. At line 270, alter CMP#10 to CMP#13. RUN and CALLgo1.

Oh dear! It looks as thought we've lost the "m" from "mystring"! But not really. The print routine finished at EQUB13 and the carriage return placed the cursor over the "m". This sequence illustrates that JSR&FFEE processes VDU codes and also that "0" need not be the loop end control but if it is intended that both VDU codes and characters are to be processed, use zero to close the loop.

LIST PROCprtb. "my string" equals eight characters (0 is a number). At line 360, change CMP#0 to CMX#8. RUN and CALLgo2. Note the prompt! If you study the routine it will be seen that the count reaches eight prior to printing the "g", therefore CMX#8 needs altering to CMX#9. Try it.

CMX is a new command. It means ComPare X. Used directly after an INX, it compares the X register count with the operand. The operand may be the content of an address, either zero page or absolute addressing. In this instance we use immediate addressing, hence the hash (CPX with the numeric 9). If the X register is not nine then the BNE returns control back to the label ".loop". A word of warning: If, in a loop, the X register is incremented twice and the initial value of X was an even number then CPX#9 will fail. There are ways of overcoming this difficulty - but not yet!

Now the classical 6502 method of controlling our loop. In PROCprtc, change line 430 to LDX#8 (this will load the X register with the numeric 8). At line 450, change INX to DEX. Delete the CMP#0 and BNEloop then replace with BPLloop. RUN and CALLgo3. "my string" is printed backwards, but this is an economical method of printing, it is also the quickest.

DEX and BPL are two more new commands. DEX means DEcrement X or, each time through the loop X is reduced by one. BPL means Branch on PLus, so whilst the register being tested is positive, control will pass to the label quoted, in this case .loop.

BRANCH had been used twice so far in BNE and BPL and is clearly more than just a descriptive word. Along with JSR and JMP, it enables control to be moved within the program. It differs from the JUMP commands in that it is subject to "two's compliment binary" (See Part Three). BRANCH will move the control up to 127 addresses forward or 128 backwards.

This limitation can be overcome messily by branching to a label and immediately following the label with LDY#0:CPY#0:BEQnext. A better method of extending a BRANCH instruction is to branch to a label which is followed by a JMPnext. Should a branch be too long, then the ERROR report "Out of range" will be generated during the two-pass assembly.

The four safest methods of printing use 0 or 10 as a delimiter but are marginally slower and use more of the commodity that the Electron lacks, memory. Using an up-count in the X register has minimal advantage over the delimiter and none if the delimiter used is the new-line character "10". A definite disadvantage is that, should the text need editing, the count must be changed as well. The down-count using BPL is the fastest, and the code is much shorter. If the countdown is in multiples of more than one, then errors can occur. For instance, LDX#1 combined with DEX. DEX means the X register will decrement to zero and, before it can be tested, will decrement again. As the X register is unable to hold a negative value, it wraps around to 255 and the loop becomes an utter nonsense.

Type: CLS:CALLgo:CALLgo1:CALLgo2:CALLgo3 (RETURN). Notice that one "my string" is missing - but why?? Look at PROCprta, line 270. Can it be cured by a different Operating System call? (JSR&????). No clues except read the handbook.

*EXEC more copies of PROCdata onto the program and experiment with the various PROCprts. Don't forget to change the label ".data" to ".data1" etc and in the PROCprts alter LDAdata,X to LDAdata1,X, etc. Relieve the tedium by changing "my string". Don't forget to count in the up or down count routines. No, I do not know a good way of spelling words (let along whole strings of them!) backwards! Perhaps that explains the spelling in commercial software?

Next time, the print routine will be changed to enable it to print the content of any string routine that CALLs it with a JSR command.

Roy Warner, EUG #6