Machine Code |
Bored with Basic? Curious about code? Our new series introduces you to the mysterious language of the Z80. Marcus Jeffery is your interpreter.
Once proficient with Spectrum Basic, many users turn to Z80 machine code as the next language to learn, usually to increase speed and decrease program size.
It is all very well spending hours mapping out new arcade games, but not very useful if they are either too big to fit into memory or too slow to be playable. Unfortunately, Z80 is very different from Basic, and the majority soon become disillusioned with the language. However, with just a little perseverance, Z80 can easily be mastered.
In this series of articles, we hope to teach the main aspects of Z80 machine code. Do not expect to be able to program the world's greatest game immediately, but each article will include an example program, using the techniques learnt, which you will be able to incorporate into your own programs to speed them up.
Z80 programs are usually found in two forms: assembly code and machine code. When writing in machine code all instructions are stored as one or two numbers in the range 0 to 255. That, however, is not very convenient for the machine code programmer, so assembly language uses a program in the computer to convert three- or four- letter codes into the machine code numbers. A few of those codes can be seen in figure four - LD, INC and DEC - and to the right of those are their numerical equivalents.
You'll have noticed that some of those numbers include the letters A to F as well as the digits 0 to 9. That is because the numbers are shown in hexadecimal form - hex - which uses base 16. Figure one shows how those digits are related to the normal decimal system - base 10. To convert a hex number to decimal, carry out the following operations:
Take a pair of hex digits - for instance, B1. Look up the first digit in figure one, and multiply its decimal equivalent by 16. So, B (in hex) is 11 (decimal) and
11 x 16 = 176
Add the decimal equivalent of the second hex digit. Thus,
176 + 1 = 177
to give the answer.
The reverse operation can be carried out by dividing a decimal number by 16 to give the first hex digit, and using the remainder for the second hex digit.
Basic programmers are used to having a fairly sophisticated language with a large number of variables. When using machine code, there are only a limited number of variables, known as 'registers'.
The main Z80 register is the A register, also known as the accumulator. That is eight binary digits - bits - long, so it can only hold a number in the range 0 to 255. Eight bits is known as one byte of information when using the Z80 processor. It is for that reason that hex numbers usually consist of two hex-digits, since that will represent a number in the range 0 to 255.
The Z80 processor has a number of other registers, the most important of which are the ones labelled B, C, D, E, F, H and L, all of which are one byte long, as with the accumulator.
Your Spectrum has either 16K or 48K of memory. K stands for kilobyte, meaning 1000 bytes. In fact, a kilobyte normally refers to 1024 bytes, because that is the maximum range which can be stored as 10 bits of information. A 48K Spectrum has, in fact, 64K of memory, but 16K of that is ROM - Read Only Memory - and is used to instruct the computer what to do when you type in commands and run Basic programs.
Now, 64K is the range 0 to 65535, which is exactly the range of a number containing 16 bits of information. That is two bytes, and usually represented as a sequence of four hex digits. Consequently, all instructions which alter a particular byte in memory must use four hex digits to specify which byte is to be changed.
We are now in a position to understand a simple machine code instruction:
LD A,(6000h)
That is shown in assembly language, but could just as easily be shown in machine code hex numbers:
3A 00 60
The English translation of that would be "Load (LD) the accumulator (A) with the contents of the hex location 6000 (6000h)". The 'h' at the end of the number means the number is in hex form. The brackets surrounding the number specify that we are interested in the contents of location 6000h, not the number itself. Thus the instruction:
LD A,1Fh
would load the number 1Fh (that is, 31) into the accumulator, but
LD A,(1Fh)
would instead load the contents of the 31st byte of memory into the accumulator.
We already know that the registers can only hold numbers in the range 0 to 255, and that memory locations - addresses - are in the range 0 to 65535. That is most inconvenient, but we can fortunately combine some of the registers together to give the correct range. The registers are paired as B + C, D + E and H + L. So, the two instructions
LD HL,6000h LD A,(HL)
would be exactly the same as our first instruction. The number 6000h is loaded into the combined H and L registers, so that the accumulator is then loaded from the contents of address 6000h.
The difficulty with learning machine code is that it is easy enough to learn the instructions, but knowing what to do with them can be quite a problem. So we will look now at a typical application using some of the instructions we have already learnt. This short machine code routine can then be easily incorporated into your own programs.
First of all, type in and run the Basic program in figure two. The program will fill the screen with random characters, then prompt you for a number. By entering numbers in the range 0 to 255, you can alter the foreground, background, brightness and flashing of the characters on the screen. You can form a number for a specific colour combination as follows.
Take a number from figure three for the foreground colour. Take another number for the background colour, multiply that figure by eight, and add it to the foreground colour number. Then add 64 if your want the character to have brightness turned on. Add 128 if you want the character to be flashing.
Each character cell on the screen has an attribute value, as given above, to define its colour. All attribute values are held in memory, starting at location 22528. Consequently, the Basic program has a loop which fills those values with the number you type in. Unfortunately, you will notice that it takes quite a while to change the screen colour. What we really need is a routine which works almost instantaneously. The assembly code for such a routine is given in figure four.
The first instruction in the listing,
LD HL,22528
loads the first location of the attribute area into the double HL register pair. Remember, the actual value 22528 is used, because we have not used any brackets.
The instruction which follows loads the number 768 into the BC register pair - that is the number of locations which have to be changed. The next instruction is a little different, because we have used brackets. That will load the contents of location 60000 into the combined DE register pair. Now each register can hold a byte of information, so register E is loaded with the byte at location 60000, and register D is loaded with the next byte at location 60001.
In fact, the only byte we are interested in is the one we put into the E register - that will be the number to which all the attributes will be changed. Nevertheless, we have to use the double register instruction, because Z80 doesn't have the instruction
LD E,(60000)
All the load instructions are given in figure six, and we'll look at those in a moment.
On each execution of the main loop of the program, the number in the E register is placed into the contents of the location in the HL register pair. That is the instruction
LD (HL),E
The first time that is executed, it will place a number into the location 22528 - the first byte of the attribute area - and will colour the top left hand character on the screen. On subsequent loops, the HL register pair will be updated using
INCrement HL
so that all characters on the screen are coloured. The last few instructions decide when to stop and the RETurn instruction passes control back to the main Basic program.
To implement this machine code routine, type in and run the Basic program given in figure five. Type in a few numbers - range 0 to 255 - when prompted, and you will see how quickly the screen colour changes. Looking closely at the data statements from line 2000 onwards, you will notice that the strings match the hex numbers in the assembly code listing. Those are decoded and placed into memory, starting at location 60000 - the first data item - by the hex loader routine - lines 1000 to 1180. It would be a good idea to save this to tape, because it will be used again in future articles.
Most of the load instructions supported in Z80 machine code are given in figure six. The ones not given concern other parts of machine code which we will cover in later articles. When using these, 'addr' stands for a two-byte location number (eg 60000); 'A' stands for the accumulator; 'rp' stands for register pair (BC, DE or HL); 'reg' stands for register (A,B,C,D,E,H or L); 'byte' is any number in range 0 to 255; 'word' is any number in the range 0 to 65535 (two-bytes).
So, for example, the instruction
LD HL,22528
used in our colour program is an example of a load instruction of the type
LD rp,word
In the next article, we will be using more of these load instructions, and combining them with 'logical' instructions to perform some remarkable and useful screen colour alterations.
|
|
|
|
|
|