| Introduction | Meetings | Members | Magazine | Articles | Programs |
SQLUG
SCOTTISH QL USERS GROUP
ASSEMBLY LANGUAGE PROGRAMMING
GEORGE GWILT with JOHN SADLER
Although BASIC may be an easier programming language than Assembly, the latter produces code that runs faster and gives more direct control over the computer. There are three ways of using Assembly Language.
1. To produce code CALL'd by BASIC. 2. To add a function or procedure to the BASIC commands. 3. To produce a complete multitasking program.
We will give examples of all three.
This month we concentrate on code to be CALL'd. The steps involved in the process are:
* Writing the code. * Assembling it. * Loading the assembled code into allocated space. * CALLing it.
WRITING THE CODE
Code which is to be CALL'd will run inside BASIC and must obey two simple rules because of this.
* The address register A6 must not be altered as BASIC uses this. * The program must return to BASIC by clearing data register D0 and by issuing RTS.
The 68008 cpu has eight address registers (A0 to A7) and eight data registers (D0 to D7).
The simplest program of all is thus:
MOVEQ#0,D0 ;This is the quickest way is zeroing D0 RTS ;Return from subroutine
ASSEMBLY
The two ways of assembling a program are first to work out yourself the binary representation of the code and POKE it into RAM, and second to use an assembler program which will produce a file containing the binary code.
LOADING AN ASSEMBLED FILE
Space at least as great as the length of the code should be reserved, e.g. by address ALCHP(length). The code is then loaded by LBYTES filename,address.
CALLing
The code is activated by CALL x,al,a2,a3,a4 . . .where x is the address where the program starts (not necessarily at 'address') and a1,a2,etc are the values placed in D1, D2, D3 etc.
In the case of our very simple example the coding is best done by hand.
Instruction HEX MOVEQ#0,D0 7000 RTS 4E75
To complete this example we would issue the commands
address=ALCHP(4) ;The code is just 4 bytes long POKE address. 112 ;$70 = 112 POKE address+1,0 ;$00 = 0 POKE address+2.78 ;$4E = 78 POKE address+3,117 ;$75 = 117 CALL address
The result of this should appear to be absolutely nothing, which is certainly a relief because POKEing around in RAM can cause the QL to crash as can CALLing the wrong address. REFERENCE BOOKS We recommend that you obtain, or otherwise have access to, publications describing the Motorola instruction set for the 68000 chip and the inner workings of both QDOS and Super-BASIC.
Examples are: Author Title Publisher PENNEL, Andrew Assembly Language Programming on the Sinclair QL Sunshine The Sinclair QDOS Companion DICKENS, Adrian QL Advanced User Guide Adder
PROGRAMS We would also recommend that you obtain an assembler and monitor programs.
Examples are:
Author Program Source GWIILT, George Assembler Slug Library Disc 145 QMON QL-Monitor Digital Precision Ltd
ASSEMBLY LANGUAGE PROGRAMMING II
by GEORGE GWILT with JOHN SADLER
This month we present a more ambitious program that can be CALL'd from BASIC. The main portion of FACTORISE_ASM is given below without the subroutines. The full program is on LIBDISK 145 along with a short BASIC procedure 'factor_bas demonstrating how to use the assembled program.
BRANCH INSTRUCTIONS
The listing contains notes sufficient to chow you the structure of the program. However since arguably the most important. single instruction is the branch (Bcc and DBcc) we thought it might be useful to expand on the condition codes There are 14 different conditions (cc) which determine whether a branch occurs or not. These are all dependent on the values of the four least significant bits of the byte long CCR (Condition Code Register) whose format is:
Bit 7 6 5 4 3 2 1 0 __________________________ | 0 0 0 X N Z V C | | | | | | EXTEND | | | | (when set it equals C) NEGATIVE | | | most significant bit = 1 ZERO | | all bits are 0 OVERFLOW | arthmetic overflow occurred CARRY arithmetic carry occurred
The X bit is used for multiple length arithmetic but never for the branch instructions and so does not concern us here. The other codes are set in different ways by different instructions. The main part of our program has used 6 Bcc instructions with conditions set by TST (twice), CMP (thrice) and BSET (once). TST sets V = C = 0 and N and Z according to the number tested. CMP sets all four codes according to the result of a subtraction of source (left hand item) from destination (right hand item). BSET affects only Z and sets this to 1 if the bit in question was 0.
The 14 tests of condition are:
EQ Z on NE Z off MI N on PL N off CS C on CC C off VS V on VC V off HI both C and Z off LS C on or Z on GE N and V are both on or both off GT as for GE with Z off LT one of N and V on, the other off LE as for LT or Z on
HI (high) and GT (greater than) both sound the same but are not! HI deals with unsigned numbers and GT with signed ones (We leave for an exercise to see how this is.)
LISTING of FACTORISE_ASM without subroutines
; A demonstration program to allow factorisation, of numbers ; up to 32767 ; This program should be CALL'd from its load address ; with Dl set to the number to be factorised.
; The factors are placed in 'n' words starting at the load ; address + 4. ; The number 'n' is at the load address + 2. ; If the number in Dl is out of range 'n' is zero. BRA.S START A short branch takes two bytes ANS DS.W 16 Reserve l6 words for the answer STATUS DC.3 0 When the primes have been set ; STATUS is made non zero ; ****************** ; * Setup * ; ****************** ; START MOVEQ #0,D5 count of factors TST.L D1 check number BEQ E_EXIT zero is wrong CMPI.L #$7FFF,D1 ($7FFF = 32767) BHI E_EXIT only 1 - 32767 is allowed LEA STATUS,A0 BSET #7,(A0) test whether Ist time (and mark not) BNE FAC_1 not 1st time BSR DO_PRIMES set all primes less than 256 in PRIMES ; ; ************************************** ; * Calculate, store and count factors * ; ************************************** ; FAC_1 LEA ANS+2,A5 Point to ANSwer + 2 LEA PRIMES,A4 Prepare to go through primes BSR DO_SQ Let upper limit of primes to ; D2.W FAC2 MOVE.W (A4)+,D4 Fetch the next prime FAC3 CMP.W D2,D4 finished? . . . BGT EXIT . . . yes MOVE.W D1,D3 EXT.L D3 N to D3.L DIVU D4,D3 D3 = N mod P|N div P SWAP D3 TST.W D3 Is P a factor? . . . BNE FAC2 . . . no BSR PUT_FAC Set the factor in ANS, set ; N=N/P in DI, increase ; the count in D5 and put the ; upper limit in D2 BRA FAC_3 ; ; **************************** ; * Complete ANSwer and exit * ; **************************** EXIT CMPI.W #1,D1 1 isn't a factor BEQ E_EXIT ADDQ.W #1,D5 Increase count of factors MOVE.W D1,(A5) Insert last factor E_EXIT LEA ANS,A5 MOVE.W D5,(A5) Set count in ANS MOVEQ #0,D0 RTS
ASSEMBLY LANGUAGE PROGRAMMING III
by GEORGE GWILT with JOHN SADLER
The second part of the listing of the program FACTORISE_ASM is given here. It consists of the subroutines missing from the first part. You might find it helpful in giving a fuller understanding of the whole and certainly you ought to be able to use it to produce an assembled program.
This month, rather than go listing more details of the program, we want. To give some idea of the thought(!) processes which were involved in developing it. The following are the contents of some of the bubbles which formed above GG's heady
Ah-ha, lets do a program to factorise numbers. It will be CALL'd by a BASIC program.
"Hm. How do we get the answers? Where will they be" (Pause) Suppose _BIN (as I call the assembled program) puts the answers somewhere in RAM then they can be PEEK'd and printed."
"Suppose I put the answers at the end of _BIN (which must be loaded somewhere in RAM - and I will know where that is). That will be annoying because the place in RAM will then depend on the length of _BIN which is unknown at present and which may alter either because I think of a better way of doing things or (more likely') I have to correct mistakes. (Pause) If I put the answers at the start of the program I could easily PEEK them' Oh but then I wouldn't easily know how to CALL the program. (I must CALL it at the first instruction to be obeyed.) (Pause) I have it! I'll start the program at the start! And the first instruction will be a branch to the real program start, leaping over the answer space (however long that is). A short branch instruction is two bytes long. That's that problem out of the way."
"My BASIC program will contain CALL asad" (assuming I have loaded BIN at asad) and PEEK asad+2 (Or perhaps PEEK_W asad+2 depending on how _BIN decides to present the answers). This decision can safely be left. It will cause no problems. Problems? (Pause)."
"Yes - I must now find another problem and then solve it. There is nothing worse than steaming ahead with a program only to find that you have to redesign a large part of it because an unsolved problem has sunk it."
I hope that in future articles we can touch on the important question of debugging in addition to completing the analysis of FACTORISE_ASM.
; *************************** ; * Factorise Subroutines I * ; *************************** ; ; DOPRIMES sets all primes less than 257 in PRIMES ; A0,A1,D0,D1 and D2 are used DO_PRIMES MOVE.L D1,D7 save D1 in D7 LEA PRIMES,A0 MOVEQ #127, D0 set D0 to count 128 DOP_1 CLR.L (A0)+ DBF D0,DOP_1 clear 128 long words . . . MOVE.W #-1,(A0) . . . and mark end of PRIMES LEA PRIMES,A0 MOVEQ #2,D0 2 is the 1st prime DOP_2 MOVE.W D0,D1 D0=Dl=current prime P D0P_5 MOVE.W D1,D2 MULU D0,D2 multiply P by P+1, P+2 etc CMPI.W #257,D2 . . until P(P+r) > 257 BGT DOP_4 end of list MOVE.W D2,D3 ADD.W D3,D3 MOVE.W #1,-4(A0,D3.W) mark compound ADDQ.W #1,D1 advance number BRA DOP_5 ; ; Now we find the next prime P ; DOP_4 ADDQ.W #1,D0 step to next number CMPI.W #16,D0 we don't need primes > 16 BGT DOP_8 the process is finished MOVE.W D0,D3 ADD.W D3,D3 TST.W -4(A0,D3.W) BNE DOP_4 not prime BRA D0P_2 next prime found ; ; Store the results ; D0P_8 MOVEQ #1,D0 LEA PRIMES,A1 DOP_6 ADDQ.W *1,D0 next number MOVE.W D0,D3 ADD.W D3,D3 TST.W -4(A0,D3.W) BMI DOP_7 last number BNE DOP_6 not prime MOVE.W D0,(A1)+ D0 is prime - store it BRA DOP_6 DOP_7 MOVE.W #-1,(A1) mark end of primes MOVE.L D7,D1 restore Dl RTS
ASSEMBLY LANGUAGE PROGRAMMING IV
by GEORGE GWILT with JOHN SADLER
We aim here to investigate the development of the first section of FACTORISE_ASM, headed "Set up". All programs, indeed all self-contained sections of programs, are likely to contain the three distinct parts, "set up", "do operation" and "end". In some ways this seems annoying because "do operation" is usually the interesting bit. Unfortunately you do have to set the scene beforehand and close the curtains at the end. Anyway since the middle part of the main program is going to involve dividing the target number (in D1) by primes these have to be available somewhere. Also we don't want numbers presented for factorisation which are out of the range 1 to 32767. This gives us two things to do in "Set up". check that D1 is in range and see that a table of primes is available. Actually there turned out to be a third item -, setting the count of number of factors to zero. Thus "MOVEQ #0,D5" though essential, was an afterthought.
The next tour instructions arrange that if DI is out of range we go to E_EXIT (whatever that may do can be determined later).
We can now concentrate on producing these primes. Being lazy GG determined that rather than work these out himself he would get the QL to do it, using the Sieve of Eratosthenes". However in the general interest of speed and efficiency Lt was decided that this would be done only the first time the program was CALLd. This was achieved by testing a bit ~n the byte called STATUS. If the bit, was on it. would mean that PRIMES had been set.
The extraordinary Instruction "BSET #7.(A0)" both sets bit 7 of STATUS (to which A0 points) and also signals whether or not it was already set. (See SQLUG 74 of June 1996.)
It seemed necessary to complete the programming of the subroutine DO_PRIMES before tackling the middle part of the main program since there would then be more certainty as to the form of PRIMES. In fact it turned out to be a list of 16-bit words containing all primes less than 257 from 2 upwards and ending with -1.
Part II of the subroutines follow and perhaps next time we will come to grips with debugging.
; **************************** ; * Factorise Subroutines II * ; ****************************
; PUT_FAC puts the new factor in D4.W to ANS, ; sets the new N to N/P in Dl, sets D2.W to the ; new limit and increases the count of factors in D5.W ; PUTFAC ADDQ.W #l,D5 count of factors MOVE.W D4,(A5)+ SWAP D3 N div P MOVE.W D3,D1 ; ; DO_SQ sets D2.W to an upper limit on primes to divide Dl.W ; uses D0 ; DO_SQ MOVE.W D1,D2 MOVEQ #l6,D0 DQ_SQ1 LSL.W #1,D2 shift until 1st . . DBMI D0,DO_SQ1 . . . non zero bit LSR.W #l,D0 halve MOVEQ #0,D2 BSET D0,D2 D2 is now an appropriate power of 2 RTS ; PRIMES DS.L 132 space for primes
ASSEMBLY LANGUAGE PROGRAMMING V
by GEORGE GWILT with JOHN SADLER
The small program to factorise numbers is now ready for use. First of all you have to assemble the code having put it into a file in suitable format by means of an editor. GWASS (on Library Disk 145) requires that you have no TABs. Most assemblers will look for LF at the end of a line. The Editor will do well. You can use Perfection provided you keep clear of TABs and remember to get non wrap (F3/F3/W) so that CHRS(10) and not CHR(206) comes at the end of a line. Also you must export the tile (F3/I/<filename>) to avoid the header information Perfection produces by CTRL/S.
The file can now be assembled. If you are using GWASS it is as well to copy the file to, say, RAM1_ because GWASS puts the resulting output to the medium on which the file resides. If you are using the GST or QUANTA Assembler insert the following line at the beginning of the listing.
SECTION CODE
and finish the listing with the line,
END
Also change the last line to
PRIMES DCB.L 132,0
Assuming the listing is in RAM1, assemble the code with the command :- RAM1 _FACTORISE_ASM -BIN -NOLINK
For other assemblers consult the instruction manual.
You should examine the listing to see if there are any errors to be corrected. It is definitely unwise to use a binary file when there are errors in the assembly. I know because I have done it!
The small BASIC procedure FACTOR_BAS (LIBDISK 145, see JUN SQLUG) loads and uses the assembled file (called factor_bin). Before trying this, however, I would suggest that you try a bit of debugging. Although this will delay the moment you are impatiently awaiting when result will flow effortlessly onto the screen it will help to prevent the annoyance of a QL crash.
To debug a program you need a monitor, such as QMON, to trace each instruction. You can do this in two ways. First you can use the monitor (which I assume is QMON) to set a break point in the program having LBYTE'd it ready for CALLing. When you do CALL it QMON will take over and you can trace one instruction at a time and examine the state of the registers after each one. (Press t' and then ENTER for each successive instruction.) If something goes wrong make sure that A7 has the value it had on entry, that A6 is unaltered then set the PC to the two instructions
moveq #0,d0 rts
and pss 'g'. With luck you are now in BASIC.
A way I prefer is to pretend that FACTOR_BIN is a program in its own right and start it by
QMON RAM1_FACTOR_BIN
You are now at the second instruction You can put the value you want in D1 by e.g.
SD1 6
You can restart by setting the PC to the beginning by
SPC s
If you hit trouble you can escape by pressing CTRL/SPACE which takes you back to BASIC from which you can RJOB the program (and wipe your brow).
| Introduction | Meetings | Members | Magazine | Articles | Programs |