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 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.
Mnemonic | Description | Symbol |
LDA | load accumulator | A=M |
from memory | ||
STA | store accumulator in | M=A |
memory | ||
ADC | add memory to | A=A+M+C |
accumulator with carry | ||
CLC | clear carry | C=0 |
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.
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. |
>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:
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.
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!
?&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] 120 END
Assemble the program:
>RUN 0E6E 0E6E 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.
Mnemonic | Description | Symbol |
SEC | set carry flag | C=1 |
SBC | subtract memory from A | A=A-M-(1-C) |
with carry |
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.
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
Name | Address | Function |
OSWRCH | &FFEE | Puts character in |
accumulator | ||
to output (VDU) | ||
OSRDCH | &FFE0 | Reads from input (keyboard) |
into accumulator |
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.
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.
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.