13 Assembler Programming

In BASIC there are operators to perform multiplication, division, iteration etc., but in assembler the only operations provided are far more primitive and require a more thorough understanding of how the inside of the machine works. The ATOM is unique in that it enables BASIC and assembler to be mixed in one program. Thus the critical sections of programs, where speed is important, can be written in assembler, but the body of the program can be left in BASIC for simplicity and clarity.

The following table gives the main differences between BASIC and assembler:

BASIC Assembler
26 variables 3 registers
4-byte precision 1 byte precision
Slow – assignment takes over 1 msec. Fast – assignments take 10 usec.
Multiply and divide No multiply or divide
FOR...NEXT and DO...UNTIL loops Loops must be set up by the programmer
Language independent of Depends on instruction computer set of chip
Protection against overwriting program No protection

However, do not be discouraged; writing in assembler is rewarding and gives you a greater freedom and more ability to express the problem that you are trying to solve without the constraints imposed on you by the language. Remember that, after all, the BASIC interpreter itself was written in assembler.

A computer consists of three main parts:

1. The memory
2. The central processing unit, or CPU.
3. The peripherals.

In the ATOM these parts are as follows:

1. Random Access Memory (RAM) and Read-Only Memory (ROM).
2. The 6502 microprocessor.
3. The VDU, keyboard, cassette interface, speaker interface...etc.

When programming in BASIC it is not necessary to understand how these parts are working together, and how they are organised inside the computer. However in this section on assembler programming a thorough understanding of all these parts is needed.

13.1 Memory

The computer's memory can be thought of as a number of 'locations’, each capable of holding a value. In the unexpanded ATOM there are 2048 locations, each of which can hold one of 256 different values. Only 512 of these locations are free for you to use for programs; the remainder are used by the ATOM operating system, and for BASIC's variables.

Somehow it must be possible to distinguish between one location and another. Houses in a town are distinguished by each 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. Thus the first few locations in memory have the addresses 0, 1, 2, 3...etc. Thus we can speak of the 'contents' of location 100, being the number stored in the location of that address.

13.2 Hexadecimal Notation

Having been brought up counting in tens it seems natural for us to use a base of ten for our numbers, and any other system seems clumsy. We have just ten symbols, 0, 1, 2, ... 8, 9, and we can use these symbols to represent numbers as large as we please by making the value of the digit depend on its position in the number. Thus, in the number 171 the first '1' means 100, and the second '1' means 1. Moving a digit one place to the left increases its value by 10; this is why our system is called 'base ten' or 'decimal'.

It happens that base 10 is singularly unsuitable for working with computers; we choose instead base 16, or 'hexadecimal', and it will pay to spend a little time becoming familiar with this number system.

First of all, in base 16 we need 16 different symbols to represent the 16 different digits. For convenience we retain 0 to 9, and use the letters A to F to represent values of ten to fifteen:

Hexadecimal digit: 0 1 2 3 4 5 6 7 8 9  A  B  C  D  E  F 
Decimal value:     0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

The second difference between base 16 and base 10 is the value accorded to the digit by virtue of its position. In base 16 moving a digit one place to the left multiplies its value by 16 (not 10).

Because it is not always clear whether a number is hexadecimal or decimal, hexadecimal numbers will be prefixed with a hash ’#' symbol. Now look at the following examples of hexadecimal numbers:

#B1

The 'B' has the value 11*16 because it is one position to the left of the units column, and there is 1 unit; the number therefore has the decimal value 176+1 or 177.

#123

The '1' is two places to the left, so it has value 16*16*1. The '2' has the value 16*2. The '3' has the value 3. Adding these together we obtain: 256+32+3 = 291.
There is really no need to learn how to convert between hexadecimal and decimal because the ATOM can do it for you.

13.2.1 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.

13.2.2 Converting Decimal to Hexadecimal

To print, in hexadecimal, the value of a decimal number, type:

PRINT &123

The answer, #7B, is printed out. The '&' symbol means 'print in hexadecimal'. Thus writing:

PRINT &#123

will print 123.

13.3 Examining Memory Locations – '?'

We can now look at the contents of some memory, locations in the ATOM's memory. To do this we use the '?’ query operator, which means 'look in the following memory location'. The query is followed by the address of the memory location we want to examine. Thus:

PRINT ?#e1

will look at the location whose address is #El, and print out its value, which will be 128 (the cursor flag). Try looking at the contents of other memory locations; they will all contain numbers 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 #80 upwards, type:

FOR N=0 TO 31; PRINT N?#80; NEXT N

The value of N is added to #80 to give the address of the location whose contents are printed out; this is repeated for each value of N from 0 to 31. Note that N?#80 is identical to ?(N+#80).

13.4 Changing Memory Locations

A word of caution: although it is quite safe to look at any memory location in the ATOM, care should be exercised when changing memory locations. The examples given here specify locations that are not used by the ATOM system; if you change other locations, be sure you know what you are doing or you may lose the stored text, or have to reset the ATOM with BREAK.

First print the contents of #80. The value there will be whatever was in the memory when you switched on, because the ATOM does not use this location. To change the contents of this location to 7, type:

?#80=7

To verify the change, type:

PRINT ?#80

Try setting the contents to other numbers. What happens if you try to set the contents of the location to a number greater than 255?

13.5 Numbers Representing Characters

If locations can only hold numbers between 0 and 255, how is text stored in the computer's memory? The answer is that each number is used to represent a different character, and so text is simply a sequence of numbers in successive memory locations. There is no danger in representing both numbers and characters in the same way because the context will always make it clear how they should be interpreted.

To find the number corresponding to a character the CH function can be used. Type:

PRINT CH"A"

and the number 65 will be printed out. The character "A" is represented internally by the number 65. Try repeating this for B, C, D, E... etc. You will notice that there is a certain regularity. Try:

PRINT CH"0"

and repeat for 1, 2, 3, 4...etc.

13.6 The Byte

The size of each memory location is called a 'byte'. A byte can represent any one of 256 different values. A byte can hold a number between 0 and 255 in decimal, or from #00 to #FF in hexadecimal. Note that exactly two digits of a hex number can be held in one byte. Alternatively a byte can be interpreted as one of 256 different characters. Yet another option is for the byte to be interpreted as one of 256 different instructions for the processor to execute.

13.7 The CPU

The main part of this chapter will deal with the ATOM's brain, the Central Processing Unit or CPU. In the ATOM this is a 6502, a processor designed in 1975 and the best-selling 8-bit microprocessor in 1979. Much of what you learn in this chapter is specific to the 6502, and other microprocessors will do things more or less differently. However, the 6502 is an extremely popular microprocessor with a modern instruction set, and a surprisingly wide range of addressing modes; furthermore it uses pipelining to give extremely fast execution times; as fast as some other microprocessors running at twice the speed.

The CPU is the active part of the computer; although many areas of memory may remain unchanged for hours on end when a computer is being used, the CPU is working all the time the machine is switched on, and data is being processed by it at the rate of 1 million times a second. The CPU's job is to read a sequence of instructions from memory and carry out the operations specified by those instructions.

13.8 Instructions

The instructions to the CPU are again just values in memory locations, but this time they are interpreted by the CPU to represent the different operations it can perform, For example, the instruction #18 is interpreted to mean 'clear carry flag'; you will find out what that means in a moment. The first byte of all instructions is the operation code, or 'op code'. Some instructions consist of just the op code; other instructions specify data or an address in the bytes following the op code.

13.9 The Accumulator

Many of the operations performed by the CPU involve a temporary location inside the CPU known as the accumulator, or A for short (nothing to do with BASIC's variable A). For example, to add two numbers together you actually have to load the first number into the accumulator from memory, add in the second number from memory, and then store the result somewhere. The following instructions will be needed:

Mnemonic Description Symbol
LDA Load accumulator with memory A=M
STA Store accumulator in memory M=A
ADC Add memory to accumulator with carry A=A+M+C
  We will also need one extra instruction:  
CLC Clear carry C=0

The three letter names such as LDA and STA are called the instruction mnemonics; they are simply a more convenient way of representing the instruction than having to remember the actual op code, which is just a number.

13.10 The Assembler

The ATOM automatically converts these mnemonics into the op codes. This process of converting mnemonics into codes is called 'assembling'. The assembler takes a list of mnemonics, called an assembler program, and converts them into 'machine code', the numbers that are actually going to be executed by the processor.

13.10.1 Writing an Assembler Program

Enter the following assembler program:

10 DIM P(-1)
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 just tells the assembler where to put the assembled machine code; at TOP in this case.
20. The '[' and ']’ symbols enclose the assembler statements.
30. Load the accumulator with the contents of the memory location with address #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 the ATOM BASIC system from the machine-code program.
80. See 20.
90. The END statement is not an assembler mnemonic; it just denotes the end of the text.

Now type RUN and the assembler program will be assembled. An 'assembler listing' will be printed out to show the machine code that the assembler has generated to the left of the corresponding assembler mnemonics:

RUN
20 824D      
30 824D A5 80 LDA #80
40 824F 18   CLC
50 8250 65 81 ADC #81
60 8252 85 82 STA #82
70 8254 60   RTS
^ ^ ^ ^ ^
| | | | mnemonic statement
| | | instruction data/address
| | instruction op code
| location counter
statement line number

The program has been assembled in memory starting at #824D, immediately after the program text. This address may, be different when you do this example if you have inserted extra spaces into the program, but that will not affect what follows. All the numbers in the listing, except for the line numbers on the left, are in hexadecimal; thus #18 is the op code for the CLC instruction, and #A5 is the op code for LDA. The LDA instruction consists of two bytes; the first byte is the op code, and the second byte is the address; #80 in this case.

Typing RUN assembled the program and stored the machine code in memory directly after the assembler program. The address of the end of the program text is called TOP; type:

PRINT &TOP

and this value will be printed out in hexadecimal. It will correspond with the address opposite the first instruction, #A5. The machine code is thus stored in memory as follows:

A5 80 18 65 81 85 82 60
TOP

So far we have just assembled the program, generated the machine code, and put the machine code into memory.

13.10.2 Executing a Machine-Code Program

To execute the machine-code program at TOP, type:

LINK TOP

What happens? Nothing much; we just return to the '>' prompt. But the program has been executed, although it only took 17 microseconds, and the contents of locations #80 and #81 have indeed been added together and the result placed in #82.

Execute it again, but first set up the contents of #80 and #81 by typing:

?#80=7; ?#81=9

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

LINK 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!

13.11 Carry Flag

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

?#80=140; ?#81=150 LINK TOP

What is the result?

The reason why the result is only 34, and not 290 as one might expect, is that the accumulator can only hold one byte. Performing the addition in hexadecimal:

Decimal Hexadecimal
140	8C
+150	+96
290	122

Only two hex digits can fit in one byte, so the '1' of #122 is lost, and only the #22 is retained. Luckily the '1' carry is retained for us in, as you may have guessed, the carry flag. The carry flag is always set to the value of the carry out of the byte after an addition or subtraction operation.

13.12 Adding Two-Byte Numbers

The carry flag makes it a simple matter to add numbers as large as we please. Here we shall add two two-byte numbers to give a two-byte answer, although the method can be extended to any number of bytes. Modify the program already in memory by retyping lines 50 to 120, leaving out the lower-case comments, to give the following program:

 10 DIM P(-1)
 20[
 30 LDA #80 low byte of one number 
 40 CLC
 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]
120 END
Assemble the program: RUN
 20 826K
 30 826E AS 80 LDA #80
 40 8270 18 CLC
 50 8271 65 82 ADC #82
 60 8273 85 84 STA #84
 70 8275 A5 81 LDA #81
 80 8277 65 83 ADC #83
 90 8279 85 85 STA #85
100 827B 60 RTS

Now set up the two numbers as follows:

?#80=#8C; ?#81=#00
?#82=#96; ?#83=#00

Finally, execute the program:

LINK TOP

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

PRINT &?#84, &?#85

The low byte of the result is #22, as 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 in to the second addition, giving:

0+0+carry = 1

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

13.13 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:

SBC Subtract memory from accumulator with borrow A=A-M-(1-C)
SEC Set carry flag	C=1

13.14 Printing a Character

The ATOM 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. The addresses of these routines are standardised throughout the Acorn range of software, and are as follows:

Name Address Function
OSWRCH OFFF4 Puts character in accumulator to output (VDU)
OSRDCH 4FFE3 Read 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 following instruction:

JSR Jump to subroutine

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

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(-1)
20 W=#FFF4
30[
40 LDA #80
50 JSR W
60 RTS
70]
80 END

The variable W is used for the address of the OSWRCH routine. Assemble the program, and then set the contents of 480 to #21:

?#80=#21

Then execute the program:

LINK TOP

and an exclamation mark will be printed out before returning to the ATOM's prompt character, because 021 is the code for an exclamation mark. Try executing the program with different values in #80.

13.15 Immediate Addressing

In the previous example the instruction:

LDA #80

loaded the accumulator with the contents of location #80, which was then set to contain #21, the code for an exclamation mark. If at the time that the program was written it was known that an exclamation mark was to be printed it 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 '@' (at) 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 previously. The op-code of the instruction specifies to the CPU whether the following byte is to be the actual data loaded into the accumulator, or the address of the data to be loaded.

Next chapter