Penguin Books

Creative Assembler How To Write Arcade Games








As one of Acornsoft's games programmers, Jonathan Griffiths was responsible for such widely popular programs as Snapper and JCB Digger for the BBC Microcomputer Model B and Acorn Electron He has prepared a cassette program, which can be used in conjunction with this book, available from Acornsoft Limited, c/o Vector Marketing Ltd, Denington Industrial Estate, Wellingborough, Northants
NN8 2RL.























Acknowledgements
Thanks are due to David Johnson-Davies for providing an earlier version of the first few chapters, Jim Dobson and Jeremy Bennett for helping in the writing of this book, and to Philippa Bush and Sharron Fellows for editing it Also, thanks are due to Orlando
M. Pilchard (Q.C.), Chris Jordan, Paul Hudson,Peter Cockerell, Dominic Verity, Jeremy San, Robert Macmillan, Paul Fellows, John Collins, Simon Hughes and Mark Holmes for proof reading


This book was written and prepared on a BBC Microcomputer Model B using the VIEW word processor



This book is dedicated to all the staff at Acornsoft























CREATIVE ASSEMBLER How To Write Arcade Games

Jonathan Griffiths






























The Penguin Acorn Computer Library is a joint venture, produced by Acomsoft Limited (in association with Pilot Productions Limited), and published by Penguin Books Limited

Penguin Books Ltd, Harmondsworth, Middlesex, England
Penguin Books, 40 West 23rd Street, New York, New York, 10010, U.S A.
Penguin Books Australia Ltd, Ringwood, Victoria, Australia
Penguin Books Canada Ltd, 2801 John Street, Markham, Ontario, Canada L3R 1~
Penguin Books (N.Z.) Ltd, 182-190 Wairau Road, Auckland 10, New Zealand

First published 1984

Copyright © Acornsoft Limited, 1984

All rights reserved
























Set in Palatino by Repro Graphics, 61 Cromwell Road, Southampton

Colour origination by RCS Graphics Ltd, 39-40 Springfield Mills, Farsley, Pudsey, Leeds, and MRM Graphics, 61 Station Road, Winslow, Bucks

Made and printed in Spain by Printer industria grafica s.a., Sant Vicenc dels Horts, Barcelona
D.L.B. 1517O-1984

Line illustrations by Rob Shone
Original photography by Nick Wright

Except in the United States of America, this book is sold subject to the condition that it shall not, by way of trade or otherwise, be lent, re-sold, hired out, or otherwise circulated without the publisher's consent in any form of binding or cover other than that in which it is
published and without a similar condition including this condition being imposed on the subsequent purchaser





















INTRODUCTION

The BASIC assembler which is available on the BBC Microcomputer and Acorn Electron is a very powerful tool for programmers. It provides a comprehensible interface between the programmer and the machine code language which the 6502 processor itself uses. Hence the programmer is able to control the machine more directly using assembler.

The main reason why people write programs in assembler rather than BASIC is probably because of the speed difference between the two. Assembler instructions can be executed extremely quickly, a program written in BASIC will take between 10 and 100 times as long. Hence assembler is particularly useful for games' programmers since it enables them to move missiles and creatures across the screen quickly and smoothly. If BASIC was used to calculate the new coordinates of each object and draw them at those positions then movement would tend to occur in jerky leaps.

However, speed is not the only factor to be taken into consideration. Assembler programming gives you more power to solve a problem than BASIC does. All high-level languages require programs to have a certain structure and this puts constraints on programs written in that language.
Sceptics may advise against using assembler on the grounds that it is too complex. It is true that operations such as multiplication and division which are easy to perform in BASIC are not as straightforward in assembler. For what might be considered a trivial task, for example multiplying a number by three, several assembler instructions are required instead of just a single BASIC one. A further problem is that there are no FOR ... NEXT or REPEAT
UNTIL loops in assembler; if you require a loop you must set one up yourself. The same applies to floating point arithmetic assembler only supports integer calculations.

My advice is that you ignore these sceptics. It isn't difficult to learn to program in assembler. The programs look much less like English than BASIC I ones do but nevertheless to someone who knows the language they are easy to understand. Like learning to do anything else, all that is required is a certain amount of knowledge and a lot of practice. This book has been written to provide the knowledge - the rest is up to you.


The book is divided into three sections, each of which has a different task to perform. The first part aims to introduce the more useful assembler instructions available for the 6502 processor, giving simple examples of how they can be used. The second part introduces some of the more complex programming techniques which are aimed in particular at people writing large assembler programs. The third part is aimed mainly at the games' programmer. It provides many useful routines and finally shows how these can all be linked together to produce a complete game.









DATA STORAGE

Before you can start writing programs in assembler you need to know a few things about how data is stored inside the computer and how that data can be accessed and changed. This chapter looks at the ways in which you can enter numbers from the keyboard and the notation which the computer uses to store these values in its memorv.

~.1 Rexadecimal notation
To most people it seems natural to use base ten when dealing with numbers. We have ten digits; 0,1,... 8,9, and can use these to represent numbers as large as we please by making the value of a digit depend on which column it is in. Thus, when we consider the number 171 the first '1' represents 100, and the second '1' represents just one. Moving a digit one column to the left multiplies its value by ten; this is why our system is called base 10 or decimal.

When entering numbers into a computer you can still use base 10 if you wish, but another base - base 16 - is also available. For reasons which should become clear as you read through this chapter, base 16 (or hexadecimal) is far more suitable for working with computers. Hence it is advisable at this stage to spend some time becoming familiar with this number system.


In base 16 we need 16 different symbols to represent the 16 different 'hexadecimal digits'. For convenience we retain the symbols 0 to 9, and use the letters A to F to represent the values of ten to fifteen.

Hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F

Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Another difference between base 10 and base 16 is what happens to a digit or hexadecimal digit when it is shifted one column to the left. Whereas we have seen that in base 10 this multiplies the value of the digit by ten, in base 16 it multiplies the value by sixteen. Hence 10 in hexadecimal represents the value sixteen.

Having two bases in which we can work can lead to confusion. Consider, for example, the number 10; as we have seen this can represent either of the values ten or sixteen depending on whether it is being interpreted as a decimal or hexadecimal number. We need a method of specifying whether a number is decimal or hexadecimal. Normally we do this by prefixing hexadecimal numbers with an ampersand (&), e.g.

&B1

The 'B' has the value 16*11 because it is in the second column to the left, and the '1' represents 1 unit; the number therefore has the decimal value
176 +1 = 177.

&123

The '1' is in the third column to the left, so it hasthe value 16*16*1, the '2' has the value 16*2 and the '3' has the value 3. Adding these together produces
256 + 32 + 3 = 291

There is no real need to leam how to convert between hexadecimal and decimal because the computer can do it for you, as shown below.

Converting hexadecimal to decimal
To print out the decimal value of a hexadecimal number, such as &123, type

PRINT &123

The answer, 291, is printed out.

Converting decimal to hexadecimal

To print, in hexadecimal, the value of a decimal number, type
PRINT ~&123

The answer, 7B, is printed out. The number printed will be in hexadecimal notation, but note that the computer doesn't use the symbol '&' when it is printing hexadecimal numbers. In this case it is obvious that the answer is a hexadecimal number but for an answer such as 79 you would need to know which base you requested the computer to use to be able to interpret the result correctly.

The symbol twiddle or, more accurately, ti(de) ~ means 'print in hexadecimal'; thus writing


PRINT~123

will print 123.

  1. Binary notation and bits
Although the computer can accept numbers in either decimal or hexadecimal notation, it uses neither of these two systems for storing the numbers in its memory. The computer's memory consists of electronic circuits that can be put into one of two different states. The two states are normally represented as 0 and 1, but they are often referred to by different terms as listed below:

0 1
zero one
Low high
clear set
off on
false true
The circuits are said to be in a 'bistable state', i.e. they are always in one of two possible states. When the digits 0 and 1 are used to refer to these two states they are termed 'binary digits', or 'bits' for brevity.
With two bits, e.g. M and N, four different states can be represented:
M N
0 0
  1. 1
  2. 0
1 1

With a 'nibble', which is four bits, 16 different values can be represented (16 = 2^4). This means that a hexadecimal digit can be represented by a four-bit binary number. The hexadecimal digits and their binary equivalents are shown in the following table:
Decimal Hexadecimal Binary
0 0 0000
1 1 0001
2 2 0010
3 3 0011
4 4 0100
5 5 0101
6 6 0110
7 7 0111
8 8 1000
9 9 1001
10 A 1010
11 B 1011
12 C 1100
13 D 1101
14 E 1110
15 F 1111

Any hexadecimal number can be converted into its binary representation by the simple procedure of converting each hexadecimal digit into the corresponding four bits, for example

Hexadecimal &19
/ \
/ \
/ \
Binary 0001 1001


Thus the binary equivalent of &19 is 00011001, (or leaving out the leading zeros which are irrelevant,
11001).

1.3 Memory locations and bytes

The computer's memory is made up of a number of 'locations', each capable of holding a value. The size of each memory location is normally referred to as a 'byte'. Each byte can hold an eight-bit number, which means that it can store any one of 256 (2^8) different values; 0... 255.

We have seen already that each hexadecimal digit requires four bits to specify it. A byte, since it contains eight bits, can therefore represent any hexadecimal number between 0 and &FF.

The bits in a byte are usually numbered for convenience, as follows:

bit number 7 6 5 4 3 2 1 0
byte 0 0 0 1 1 0 0 1


Bit 0 is often referred to as the 'low-order bit' or 'least-significant bit', and bit 7 as the 'high-order bit' or most-significant bit'.

1.4 More about memory locations

Somehow it must be possible to distinguish between one location and another. Houses in a town are distinguished by each of them having a unique address. Even when the occupants of a house change, the address of the house remains the same. Similarly, each location in a computer has a unique 'address' consisting of a number which remains unchanged even when the contents of the memory location are altered. Thus we can speak of the 'contents of location 100' as being the number found in the location whose address is 100. The memory locations start from address 0 and could look something like this:

Decimal value of the number being stored
27 35 6 91
Address 0 1 2 3

An address can be one or two bytes long. This means that addresses can cover the range 0 to &FFFF. For a detailed look at which part of the memory each address corresponds to see the memory maps in Appendix A.

Examining memory locations

We can look at the contents of some memory locations in the computer using the query (?) operator. The reference '?X' means use the value of X as the address of the location under consideration. Hence the reference '?&FFEE' means that we are concerned with the location whose address is &FFEE. To look at this location type

PRINT ?&FFEE

This prints out the value found at the location specified, which in this case should be the number 108. Any memory location can be examined in this way and all of them will contain a number between 0 and 255.

It is often convenient to look at several memory locations in a row; for example, to list the contents of the 32 memory locations from &70 upwards, type
FOR N = 0 TO 31 : PRINT ?(N+&70); :NEXT N

An alternative way of writing this is

FOR N=0 TO 31 : PRINT N?&70; : NEXT N
This method is tidier than the other and gives

identical results; i.e. for each of the values of N between 0 and 31, N is added to the number &70 to give the address of the location whose contents are to be printed out. This should result in the contents of 32 memory locations being listed on the screen.




Changing memory locations

It is possible to change the number stored at a particular memory location by assigning a new value to it. As an example try changing the contents of &70. First, print the contents of this address; the value there will be whatever was in the memory when the computer was switched on since the computer does not use this location for storing any of the variables it is working with. To change the contents to 7, type

?&70=7

To verify the change, type

PRINT ?&70

Try setting the contents of this memory location to other numbers. Setting the contents to a number greater than 255 or &FF will result in the number entered modulo 256 being stored there, for example

? &70=600
PRINT ?&70

This will print out

(600 MOD 256)

A word of warning: Before you change the contents of any other memory locations be sure that vou know what you are doing. Although it is quite safe to look at almost any memory location in the computer, care must be exercised when changing any of them. The example given here uses a specific location which is not used by the computer; if you change any other location you may lose any program you have in memory or confuse the computer to such an extent that it proves necessary to reset it by pressing BREAK to make it accept any further commands.

1.5 Negative numbers - two's complement

Although the values stored in the memory locations are between 0 and 255 these can be used to represent both positive and negative numbers. To do this two's complement representation is used. To represent a number using this system we first have to consider what its positive counterpart is in binary notation. For example to find out how -5 would be stored consider the number
+5 = 00000101
We then find the complement of this, i.e. change each 0 into a 1 and each I into a 0, e.g.

complement of +5 = 1111010

Finally we add one: 11111010
+
1
11111011

This gives us the two's complement representation of -5.

We can now try adding together +5 and -5 to see if they give us 0.
00000101
11111011
(1) 00000000

Ignoring the 1 which has overflowed gives us the result, zero, which we were expecting.

Note that when representing numbers using two's complement notation a single byte can represent any number between -128 and +127. The left-hand bit is 1 if the number is negative and 0 otherwise. Zero is classed as a non-negative number.

1.6 Storing text

If locations can only hold numbers between 0 and
255, how is text stored in the computer's memory?
The answer is that numbers are used to represent thedifferent characters. Hence text is stored simply as a sequence of numbers in successive memory locations. The computer does not become confused about whether a number is representing an actual number or a character since the context will always make it clear how it should be interpreted.

The unique number corresponding to each character is given by its ASCII code (American Standard Code for Information Interchange). To find the ASCII code of a given character the ASC function can be used, for example type

PRINT ASC “A

and the number 65 will be printed out. This means that the character 'A' is represented internally by the number 65. If you try repeating this process for B C D you will notice that there is a certain regularity. The same is true for a b c~... and the sequence 1 2 3 4

A full table of the ASCII codes used to represent all the characters is given in Appendix A.





















2 CARRYING OUT INSTRUCTIONS

The previous chapter showed how characters and numbers are represented in the computer's memory, i.e. how the computer deals with data storage. However, data on its own is useless to a computer, it also needs instructions telling it what to do with the data. This chapter looks at how the computer handles instructions and explains some of the simpler assembler instructions which it can use. The instructions listed refer only to the 6502 processor so if you have any other sort of processor connected to your machine, e.g. a Z-80 second processor, this will have to be disabled before attempting any of the routines given in this and subsequent chapters.

2.1The CPU
The Central Processing Unit or CPU is the computer's brain. It is the most active part of the computer; although areas of memory can remain unchanged for hours on end when a computer is being used, the CPU is working all the time the machine is switched on. The CPU's job is to read a sequence of instructions from memory and carry out the operations specified by those instructions.

The instructions which the CPU acts on are just values stored in memory locations. The CPU takes a byte and interprets it as an instruction, e.g. &18 will be interpreted to mean 'clear carry flag'; this will be explained later in this chapter. It then performs the operation as instructed and goes on to collect the next byte.


The CPU, the computer's brain.
The first byte of all instructions is the operation code, or 'op-code'. Some instructions, such as the example above, consist of just the op-code; other instructions require data on which they must operate. These instructions therefore consist of two or three bytes, the first one being the op-code and the other one or two consisting of data. For example the value &E6 is translated into the instruction 'increase the contents of the memory location with the following one-byte address by one'. Hence the CPU then takes the next byte from the memory and interprets this, not as an instruction, but as the address of the location whose contents are to be incremented. It then adds one to the number stored in that location. Having executed this instruction, the CPU then goes on to the next byte which is taken to represent the next instruction it must perform.

2.2Machine code V assembler
The above few paragraphs should have given you the idea that everything the CPU acts upon is a number between 0 and &FF (255 decimal); each number being interpreted by the CPU as an instruction or some data which an instruction must use. The list of numbers which are being used are referred to as machine code. It is possible for us to talk to the computer in its own language, i.e. program in machine code, but this would mean that we would have to know which instructions all the op-codes stand for. Programming in assembler alleviates the need for learning all these translations. In assembler each op-code is represented by a three letter mnemonic, e.g. CLC is used instead of &18 to give the instruction 'clear carry'. The computer then converts all the mnenomics into the corresponding op-codes.

This process is carried out by an assembler and hence is known as 'assembling'. The program that the assembler takes as its input is known as the source code, and the machine code output is referred to as the object code.

2.3 The accumulator and the carry flag
The accumulator is just a temporary location inside the CPU which plays a part in many of the operations performed by the CPU. For example, to add two numbers together you have to load the first number into the accumulator from the memory, add in the second number from memory, and then store the result somewhere. To do this the following assembler instructions will be needed:

Mnemonic Description Symbol

LDA load accumulator from A=M
memory
STA store accumulator in M=A
memory
ADC add memory to A=A+M+C
accumulator with carry
CLC clear carry C=0

The carry is needed to allow numbers greater than one byte (255 or &FF) to be generated. When an eight-bit value is added to another eight-bit value the result could be too great to be represented by eight bits, e.g. 140 +160 = 300 (>255).

In order to allow for this, the CPU will use the carry as the ninth bit of the accumulator, and thus the carry will contain the extra bit. In the above example, when the numbers 140 and 160 are added together and the result stored in a memorv location, this location will contain the value 44 (300 MOD 256). By using the carry flag you will have a record of whether the result of the addition was actually the value 44 or if it was 300. Hence, to avoid confusion, clear the carry before performing any additions.

2.4 Writing an assembler program
Enter the following assembler program:

10 DIM P% 100
20[
30 LDA &80
40 CLC
50 ADC &81
60 STA &82
70 RTS
80]
90 END

The meaning of each line in this assembler program is as follows:

10 The DIM statement is not an assembler mnemonic; it is a BASIC instruction to tell the assembler where to put the assembled machine code by DIMensioning off an area of memory for it. The DIM statement is followed by a number (not in brackets) and the statement reserves this number of bytes for the machine code which will be generated. As a rough guide to the amount of room needed count the number of assembler instructions used, treble it and reserve at least this number of bytes.

The BASIC variable P% is used by the assembler as a location counter to specify the next free address. Hence the statement sets P0/o to the lowest address of the reserved block of memory and then as each byte of machine code is generated, P0/o increases by one byte so that it always points to the next free location.

20 The '[' symbol is an 'assembler delimiter' which has to be used immediately before the first assembler statement to tell the BASIC interpreter that the following statements will be in assembler rather than BASIC.

30 Load the accumulator with the contents of the memory location whose address is &80. (The contents of the memory location are not changed.)

40 Clear the carry flag.

50 Add the contents of location &81 to the accumulator with the carry. (Location &81 is not changed by this operation.)

60 Store the contents of the accumulator to location &82. (The accumulator is not changed by this operation.)

70 The RTS instruction will usually be the last instruction of any program; it causes a return to BASIC from the machine-code program. The mnenomic stands for 'return from subroutine'.

80 The ']' symbol is an assembler delimiter which has to be used after the last assembler instruction to tell the interpreter that the following statements will be in BASIC.

90 The END statement is not an assembler mnemonic; it just denotes the end of the program.

Now type RUN and the assembler program will be assembled; the assembled code being inserted directly in memory at the address specified by P0/o.
An 'assembler listing' will be printed out to show the machine code the assembler has generated to the left of the corresponding assembler mnemonics:

>RUN
OE5D
OE5D A5 80 LDA &80
OE5F 18 CLC
0E60 65 81 ADC &81
0E62 85 82 STA &82
0E64 60 RTS
operand
mnemonic statement
instruction data/address
instruction op code
location counter statement

The program has been assembled in memory starting at &0E5D, immediately after the program text. This address may be different when you enter the example program if you have inserted extra spaces into the program or if you have filing systems other than cassette in your machine, but that will not affect any other part of the listing. All the numbers in the listing are in hexadecimal; thus &18 is the op-code for the CLC instruction, and &A5 is the op-code for LDA when the number being loaded is not given directly but is obtained by looking in the memory location whose one-byte address is given. Hence this LDA instruction consists of two bytes; the first byte is the op-code, and the second byte is the address; &80 in this case.

Another method of finding out where the machine code is, is to find out where 'TOP' is by typing

PRINT ~TOP

This value gives the address of the memory location immediately after the program text. Since the machine code follows on straight after the text this address is the one corresponding to the first instruction, &A5. Thus the machine code is stored in memory as follows:

A5 80 18 65 81 85 82 60
^
TOP

When 'RUN' was typed this assembled the assembler program and put the machine code produced into the computer's memory, however it did not execute the program. The method for doing this is described below.

2.5 Executing a machine-code program

To execute the machine-code program at TOP, type

CALL TOP

Nothing obvious will happen except for the '>' prompt being printed again on the screen. This indicates that the computer has finished executing the program and hence the contents of locations &80 and &81 will have been added together and the results placed in &82.

You can verify this by setting the contents of &80 and &81 to certain values by typing, for example

?&80=7 : ?&81=9

If you wish you can also set the contents of &82 to 0. Now type

CALL TOP

and then look at the contents of &82 by typing

PRINT ?&82

The result is 16 (in decimal); the computer has just added 7 and 9 and obtained 16

2.6 Adding two-byte numbers

Try executing the program for different numbers in
&80 and &81. You might like to try the following:

?&80=140 : ?&81=160 CALL TOP

We saw earlier in this chapter that if an addition generates a number greater than 255 then the result stored in the memory location specified will be that number modulo 256. Hence the result in this case will be 44 rather than 300. Here is the calculation in hexadecimal:
160 &A0
140 &8C
300 &12C

Only two hex digits can fit in one byte, so the '1' of &12C is lost, and only the &2C is retained. Luckily the '1' carry is retained for us in the carry flag as was mentioned earlier, though we didn't see then how to use this. The example below shows how the two numbers can be treated as being two-byte numbers and added together using the carry to produce a two-byte number which is the complete answer. This method can be extended to any number of bytes since the carry flag makes it a simple matter to add together two numbers as large as we please. Modify the program already in memory by retyping lines 50 to 120, if you wish (leaving out the comments to the right of the assembler text). Here is the modified program:

10 DIM P% 100
20[
30 LDA &80 Low byte of one number
40 CLC Clear carry flag
50 ADC &82 low byte of other number
60 STA &84 low byte of result
70 LDA &81 high byte of one number
80 ADC &83 high byte of other number
90 STA &85 high byte of result
100 RTS
110]
123 END

Assemble the program:

>RUN
OE6E
OE6E AS 80 LDA &80
0E70 18 CLC
0E71 65 82 ADC &82
0E73 85 84 STA &84
0E75 AS 81 LDA &81
0E77 65 83 ADC &83
0E79 85 85 STA &85
OE7B 60 RTS

Now set up the two numbers as follows:

?&81=&8C : ?&81=&00
?&82=&A0 : ?&83=&00

Finally, execute the program by typing

CALL TOP

and look at the result, printing it in hexadecimal this time for convenience:

PRINT ~?&84, ~?&85
The low byte of the result is &2C, as was obtained

before using the one-byte addition program, but this time the high byte of the result, &1, has been correctly obtained. The carry generated by the first addition was added into the second addition, giving

0+ 0 + carry = 1

Try some other two-byte additions using the new program.

2.7 Subtraction

The subtract instruction is just like the add instruction, except that there is a 'borrow' if the carry flag is zero. Therefore to perform a single-byte subtraction the carry flag should first be set with the SEC instruction.

Mnemonic
SEC set carry flag C=1
SBC subtract memory from A=A-M-(1-C)
A with carry

Example

10 DIM P% 100
20[
30 LDA &80 Low byte of first number
40 SEC Initialise carry flag
50 SBC &82 Low byte of other number
60 STA &84 Low byte of result
70 LDA &81 Now do high bytes
80 SBC &83
90 STA &85
100 RTS Return
110]
120 END

Note that the above program is very similar in structure to the addition example in section 2.6.

2.8 Comments
There are two methods of putting comments in assembler programs. The first of these, which is used in previous examples, is to put the comment after an assembler instruction, separated from it by one or more spaces, e.g.

60 STA&84 low byte of result

Alternatively a statement may start with a backslash (\), in which case the remainder of that statement is ignored, e.g.

65 Now for the high bytes

Note that a colon (:) will end the comment and start a new assembler statement, for example line 60 could be replaced by

60 Low byte of result STA &84

2.9 Printing a character
The computer contains routines for the basic operations of printing a character to the VDU, and reading a character from the keyboard, and these routines can
be called from assembler programs.
Name Address Function
OSWRCH &FFEE Puts character in accumulator
to output (VDU)
OSRDCH &FFEO Reads from input (keyboard)
into accumulator

In each case all the other registers are preserved. The names of these routines are acronyms for ‘operating system write character' and 'operating system read character' respectively. These routines are executed with the instruction JSR (jump to subroutine).

A detailed description of how the JSR instruction works will be left until the following chapter.

The following program outputs the contents of memory location &80 as a character to the VDU, using a call to the subroutine OSWRCH:

10 DIM P% 100
20 oswrch=&FFEE
30[
40 LDA &80
50 JSR oswrch
60 RTS
70]
80 END

The variable 'oswrch' is used for the address of the OSWRCH routine. Assemble the program, and then set the contents of &80 to &21 by typing

?&80=&21

Then execute the program using

CALL TOP

and an exclamation mark will be printed out before returning to the computer's prompt character, because &21 is the code for an exclamation mark. An alternative method of setting the contents of location &80 to &21 is therefore

?&80=ASC"!"

Try executing the program with different values in &80, with values chosen from the table of ASCII values in Appendix A.

2.10 immdiate addressing
In the previous example the instruction

LDA &80

loaded the accumulator from the location whose address is &80, this is known as 'absolute' addressing. The location was then set to contain &21, the code for an exclamation mark. If at the time the program was written it was known that an exclamation mark was to be printed in would be more convenient to specify this in the program as the actual data to be loaded into the accumulator. Fortunately an 'immediate' addressing mode is provided which achieves just this. Change the instruction to

LDA #&21

where the '#' (hash) symbol specifies to the assembler that immediate addressing is required. Assemble the program again, and note that the instruction op-code for 'LDA #&21' is &A9, not &A5 as it was previously for the absolute addressing. The op-code of the instruction specifies to the CPU whether the following byte is the actual data loaded, or the address of the location containing the data.

2.11 Using addresses
So far when a value has been saved, a numerical address has been used to define where it is to be stored, e.g.

STA &80

A better method of giving an address is to use a variable name, e.g.

STA addr

In this case 'addr' must be specified at the beginning of the program, e.g.

addr = &80

This method is better than the previous one since it makes the program easier to understand, i.e. an address can be given a relevant name, e.g. 'xlowbyte' or 'yhighbyte'. In addition, changing the location of a value becomes easier, since only the initial specification need be altered rather than every occurence of that value throughout the program.

The locations used must be chosen carefully to avoid corrupting operating system or BASIC workspace. The memory map in Appendix A should help to show which locations can be used in different circumstances. Also there are some locations which are always free when using BASIC; these are &70 to &8F.

3 JUMPS,BRANCHES AND LOOPS

When an assembler program has been assembled and is being executed, the address of the next instruction to be executed is kept in a register called the 'program counter'. All the programs met so far have been executed in the order that the instructions were written, so the program counter has just steadily increased until it reached the last instruction. This chapter introduces the jump and branch instructions which can make the program counter jump over instructions or move back to previous ones to execute them again. These instructions make it possible to implement loops and perform different instructions depending on the outcome of previous ones.

3.1 Jumps
Ordinary jumps
Mnemonic Description
JMP jump to instruction whose address is given

The JMP instruction is followed by the address of the instruction to be executed next, e.g.
JMP &E48 or JUMP addr

Instead of describing the address by a number, we can use a 'label' to indicate to the assembler where we want to go. In the assembler, labels are variables prefixed with a full stop (.).

10 oswrch =&FFEE
20 DIM P% 100
30[
40.enter
50 LDA #ASC”*”
60..Loop
70 JSR oswrch
80 JMP Loop
90]
100 END

When the program is assembled the address corresponding to the label '.loop' will be inserted in the machine code. When the code is executed the value of the program counter will be set to this address and the CPU will collect its next instruction from the location with that address and will continue executing from there.

The label '.enter' at the start of the program has been included so that this label can be called in order to execute the program. This is a better way of executing a program than calling TOP since TOP doesn't always point to the first machine code instruction. This is true for the above example since TOP will point to the assignment statement ‘oswrch=&FFEE'.

The program will output an asterisk (*), and then jump back to the previous instruction. The program has become stuck in an endless loop! Compare this program with the following BASIC program:

10 A
20 VDU A
30 GOTO 20

To get out of the BASIC loop you press ESCAPE. This will not automatically halt machine code programs, however. To exit from a machine code loop without losing the program you must press BREAK, and then type 'OLD' to retrieve the original program.

Jumps to subroutines

Mnemonic Description
JSR jump to subroutine

Examples of this instruction have been used previously. Like the JMP instruction it is followed by a two-byte address, e.g.

JSR oswrch

In this case the address of the instruction directly following the JSR instruction in the code is noted, and then the value of the program counter is set to the address of 'oswrch'. The CPU will go to this address for its next instruction and start executing the code from there until it meets an RTS. This will set the program counter to the address which was noted earlier so that the CPU can then continue executing the code following the JSR instruction. Subroutines jumped to can either be part of the assembler program or, as in this example, sub-routines which exist in the operating system memory.

3.2 The zero and negative flags
There are several flags in the CPU which can be set or cleared depending on the outcome of certain instructions. The carry flag was introduced in the previous chapter, this is set or cleared as the result of an ADC (add with carry) instruction. Another very useful one is the zero flag, called Z. This is set if the result of the previous operation gave zero, and is cleared otherwise, e.g.

LDA &8O

would set the zero flag if the contents of &80 were zero.

Similarly the negative flag, N, is set if the result of the previous operation was negative in two's complement notation, i.e. if the top bit was set, e.g.

LDA &80

would set the negative flag if the number stored in location &80 was greater than 127 (01111111).

The conditions of all the flags are stored in a byte called the status register (P), and each flag is represented by one bit: e.g. the top bit of the status register is set if N=1 and the bottom bit is set if C=I.

3.3 Conditional branches
Conditional branches enable the program to act on the outcome of an operation. There are eight different branch instructions, six of which are introduced.

Mnemonic Description Status
BEQ branch if equal to zero (ie Z=1)
BNE branch if not equal to zero (ie Z=0)
BCC branch if carry clear (ie C=0)
BCS branch if carry set (ie C=1)
BPL branch if plus (ie N=0)
BMI branch if minus (ie N=1)

The conditional branch instructions test the state of the various condition flags, e.g. the zero flag and negative flag. If the condition is not satisfied then it carries on executing, but if the condition is satisfied then the computer goes to the place indicated by the byte following the branch op-code. This byte is stored as a relative address, thus if you say

BCS notzero

the assembler works out the difference (in bytes) between the current instruction and the place where the label '.notzero' is, and puts this value after the op-code. This means that the value of this byte is used, in conjunction with the address of the current instruction, to tell the CPU where to go next.

Because only a single byte is allowed in this relative addressing mode, the branch instructions can only point to one of 255 nearby bytes. The two's complement representation of numbers is used to give the offset relative to the current address. Branches which point forwards are restricted to 0-127 bytes beyond the current location. The value of the byte following the op-code for these is then 0-127. Branches which point backwards to places at lower addresses in memory require a negative value to be added to the current location. These use the numbers 128-255 to represent the values -128 to -1.

The JMP instruction does not use relative addressing; it is followed by two bytes which specify the absolute address which will be the destination. Hence the branch instruction is shorter than the jump instruction, the jump being three bytes long (op-code and two-byte address) and the branch being two bytes long (op-code and one-byte offset). This difference is automatically looked after by the assembler.

The following simple program will print an

exclamation mark if 'character' contains zero, and a star if it does not. The comments to the right of the assembler statements may be omitted when you enter the program.

10 DIM P% 100
20 character=&80
30 oswrch = &FFEE
40[
50.enter
60 LDA character
70 BEQ exclamation If zero print '!’
80 LDA #ASC"*" Star
90 JSR oswrch Print it
100 RTS Return
1 10.exc Lamat ion
120 LDA #ASC"!" ExcLamation mark
130 JSR oswrch Print it
140 RTS Return
150]
160 END

Note that the above program can be made shorter, by replacing the instructions

JSR oswrch
RTS

with the single instruction

JMP oswrch

Replacing JSR and RTS instructions by a JMP to a subroutine reduces the size of both a source program and the object code it produces, and hence increases execution speed.

Now assemble the program by typing RUN. You should get the message:

No such variabLe at Line 70

This is because the assembler processes the mnemonic instructions in the order in which they are listed in the program. Therefore when it encounters 'BEQ exclamation' it has not yet found the label exclamation' so it cannot work out the offset which is required in the following byte. This is known as the forward-reference problem, and is easily overcome using the method of two-pass assembly which is explained below.

3.4 Two-pass assembly
When a program contains forward references it needs to be assembled twice. During the first pass of the assembler the addresses of all the labels are noted so that during the second pass the offsets of the branch instructions can be included. And the assembler must be told not to worry when, during the first pass, it comes across errors of the sort indicated above.

This can be done using the OPT statement, an assembler directive which has a single parameter for which the following values are possible:

OPT 0 No error messages, and no listing
OPT 1 No error messages, and listing
OPT 2 Error messages reported, and no listing
OPT 3 Error messages reported, and listing (Default)

Thus to suppress messages and a listing on the first pass, and to restore them on the second pass, we need to use OPT 0 and OPT 3 respectively. This can be effected by placing the directive inside a FOR NEXT loop, which goes from 0 to 3 in steps of 3. Then

The value of the control variable is used as the parameter of the OPT statement. So, to alter the program which was given above, simply enter these lines:

10 DIM code 100
23 FOR pass = 0 TO 3 STEP 3
26 P% = code
30[ OPT pass
145 NEXT pass

This time the error message will not be produced and the correct offset will be calculated for the branch instruction.

Note lines 10 and 26, which replace the old 'DIM P% 100' statement. P% must be reset to the starting value each time that the code is assembled.

Now execute the program by typing

CALL enter

and verify that the program behaves as it should for different values in &80.

  1. XandYregisters

The CPU contains two registers, called the X and Y registers, in addition to the accumulator. As with the accumulator, there are instructions to load and store the X and Y registers:
Mnemonic Description Symbol

LDX load X register from memory X=M
LDY load Y register from memory Y=M
STX store X register to memory M=X
STY store Y register to memory M=Y

However, unlike the accumulator, the X and Y registers cannot be used as one of the operands in arithmetic instructions; they have their own special uses which will be outlined later.

The X and Y registers are particularly useful as the control variables in iterative loops, because four special instructions exist which will either increment (add 1 to) or decrement (subtract 1 from) their values.
Mnemonic Description Symbol

INX increment X register X""X+1
INY increment Y register Y=Y+1
DEX decrement X register X""'X-1
DEY decrement Y register Y=Y-1

Note that these instructions do not affect the carry flag: incrementing &FF will give &00 without changing the carry bit. The zero and negative flags are, however, affected by these instructions.

  1. Iterative loops

The iterative loop enables the same set of instructions to be executed a fixed number of times, e.g.

10 DIM P% 100
20 oswrch = &FFEE
30E
40.enter
50 LDX #8 InitiaLise X
60 LDA #ASC"*" Code for star
70. Loop
80 JSR oswrch Output star
90 DEX Count it
100 BNE Loop ALL done?
110 RTS
120 END

Assemble the program by typing RUN. This program prints out a star, decrements the X register, and then branches back if the result after decrementing the X register is not zero. Consider what value X will have on successive trips around the loop and predict how many stars will be printed out; then execute the program with 'CALL enter' and see if your prediction was correct. (If you were wrong, try thinking about the case where X was initially set to 1 instead of 8 in line 50.)

How many stars are printed if you change the instruction on line 50 to 'LDX #0'?

3.7 Comparing values
In the previous example the condition X=0 was used to terminate the loop. Sometimes we might want to count up from 0 and terminate on some other specified value. The compare instruction can be used to compare the contents of a register with a value in memory; if the two are the same, the zero flag will be set. If they are not the same, the zero flag will be cleared. The compare instruction also affects the carry flag by setting it to 1 if the register is greater than or equal to the value in memory, and 0 otherwise.

Mnemonic Description Symbol

CMP compare accumulator with A-M
memory
CPX compare X register with X-M
memory
CPY compare Y register with Y-M
memorv

Note that the compare instruction does not affect its two operands, it just changes the flags as a result of the comparison.

The next example again prints eight stars, but this time it uses X as a counter to count upwards from 0 to 8.

10 DIM P% 100
20 oswrch=&FFEE
30[
40.enter
50 LDX #0 Start at zero
60..Loop
70 LDA #ASC”*” Code for star
80 JSR oswrch Output star
90 INX Next X
100 CPX #8 ALL done?
110 BNE Loop If not then repeat
120 RTS Else return
130]
140 END

In this program X takes the values 0, 1, 2, 3, 4, 5, 6, and 7. The last time around the loop X is incremented to 8, and the loop terminates. Try drawing a flowchart for this program.

3.8 Using the control variable
In the previous two examples X was simply used as a counter, and so it made no difference whether we counted up or down. However, it is often useful to use the value of the control variable in the program. For example, we could print out the character in the X register each time around the loop. The order in which we want the characters would then determine whether we count up or down. We therefore need a way of transferring the value in the X register to the accumulator so that it can be printed out by the OSWRCH routine. One way would be to execute:

STX tempaddr
LDA tempaddr

where 'tempaddr' is not being used for any other purpose. However, there is a more convenient way, using one of four new instructions:

Mnemonic Description Symbol

TAX transfer accumulator to X register X=A
TAY transfer accumulator to Y register Y=A
TXA transfer X register to accumulator A=X
TYA transfer Y register to accumulator A=Y

Note that the transfer instructions only affect the register being transferred to.

The following example prints out the alphabet by making X cover the range A to Z.

10 DIM P% 100
20 oswrch = &FFEE
30[
40.enter
50 LDX #ASC"A" Start with the Letter A
60.Loop
70 TXA Put it in the accumuLator
80 JSR oswrch Print it
90 INX Next one
100 CPX # (ASC"Z”+1) Finished ?
110 BNE Loop If so - continue
120 RTS ELse return
130]
140 END

All these examples could have used Y as the control variable instead of X in exactly the same way.

3.9 Conditional assembly
Assembler source text can contain tests, and assemble different statements depending on the outcome of these tests. This is especially useful where slightly
Different versions of a program are needed for many different purposes. Rather than creating a different source file for each different version, a single variable can determine the changes using conditional assembly, e.g

10 DIM CODE%100
20 char=&70
30 oswrch=&F FEE
40 osrdch=&FFEO
50 beLL=7 Bleep = VDU 9
60 prompt=ASC":”
70 INPUT"bell",beLL$ Input 'bell$'
80 bellflag=INSTR("Yy",bell$) bellflag' is true if
90 FOR pass=0 TO 3 STEP 3 bell$ = 'y' or 'Y'
100 P%=CODE%
1 10[ OPTpass
120.enter
130]
140 IF bellflag THEN [OPT pass:LDA #bell :JSR oswrch:]
150[OPT pass
160 LDA #prompt Load A with ':' prompt
170 JSR oswrch Print from A
180 JSR osrdch Read in a character
190 STA char store it at char and
200 JSR oswrch ,print it out
210 RTS
220]
230 NEXTpass

When this program is run it asks if you want the computer to bleep or not and sets 'bellflag' accordingly. Then when the machine code is executed it inputs a character from the keyboard, bleeping if 'bellflag' is set to remind you that an input is required, and prints out a character corresponding to the first key pressed. This character is also saved at the address 'char'.

4LOGICAL OPERATIONS, SHIFTS AND ROTATES


We have seen previously that each byte or memory location is made up of eight bits, each of which can be set to the value 0 or 1. Although the operations we have considered so far have treated the whole byte as the smallest quantity being dealt with, many operations in the computer's instruction set are best considered as operations which act on eight separate bits. Some of these perform such important tasks as changing the case of characters, or multiplying and dividing.

4.1 Logical operations
Logical operations are performed between the individual bits of two operands; one of the operands is always the accumulator and the other is a memory location or immediate value. In this section three such