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