Machine Code |
Marcus Jeffery continues his voyage into the realms of the Z80 and discovers a calculating machine
Last month, we looked at the 'load' instructions available using the Z80 instruction code. Now we will cover a few of the necessary evils - machine code arithmetic! We'll also use a couple of these arithmetic instructions to implement a high class screen clearing routine, which you'll be able to use in your own programs.
We have already seen an arithmetic statement in last month's routine:
INC HL ;HL = Next byte in file area
As you probably guessed, that has the effect of adding one to the HL register pair. Remember, we didn't use any brackets. So the HL registers are changed. Not the contents of the location which they address. The instruction:
INC (HL)
would have a different effect. The sequence of instructions in figure one may make this clearer.
We can increment the other register pairs in the same way, with:
INC BC
and
INC DE
but there are no instructions to change the contents of these addresses by surrounding them with brackets. In addition, there are increment instructions for all the single registers, so:
INC A
would add one to the A (accumulator) register.
As mentioned in the first article, each register can only hold a number in the range 0 to 255, so what happens if we try to increment a register which already holds the number 255? If we look at this in binary,
11111111b + 1 = 100000000b
giving a result of nine bits. Since a register can only hold eight bits, the top bit is lost, leaving a result of zero. In other words, when a register is incremented over its maximum value, it wraps around to zero again - there are no helpful 'Out of range' messages like Basic.
Exactly the same thing happens when you increment register pairs, but here the allowed range is 0 to 65535. It is interesting to examine the lower byte of the register pair, because it acts in the same way as a single register, wrapping around to zero. However, here the ninth bit is not lost, but carried over to be added to the high byte register. So
LD B,0 ;Top byte contains zero LD C,255 ;Lower byte is set to the maximum value INC BC ;BC should now be 256,
which means B now contains the value 1 and C has wrapped around to zero.
In much the same way as increment works, you can decrement registers, which subtracts one from the register contents. Values wrap around in the same way too, so
LD A,0 ;A=0 DEC A ;A=255
All of the INCrement and DECrement instructions are displayed in detail in figure nine.
Of course, it is not always the case that you only want to add or subtract the value '1'; you may want to add, say, five to a register. You could do that by forming a loop to add or subtract a certain number of times, but fortunately Z80 contains more generally useful add and subtract commands. The instruction:
ADD A,5
would add five to the accumulator. Most processing is carried out in the A register (accumulator - hence its name), so there are no equivalent instructions for other registers. You can add registers to the accumulator using the instructions
ADD A,reg (reg = A,B,C,D,E,H or L)
Yes, it's even possible to add the accumulator to itself with the instruction:
ADD A,A
If no wrap around occurs, then this will have the effect of doubling the contents of the A register.
Another useful ADD instruction is
ADD A,(HL)
which will add the contents of the byte location addressed by the HL register pair to the accumulator. This may sound a bit of a mouthful, but to make it clearer:
LD A,8 ;A = 8 LD HL,123 ;HL = 123 LD (HL),5 ;The location 123 now contains 5 ADD A,(HL) ;A = 13 (8+5), not 131.
Finally, you can add register pairs together, as long as the result is in the HL register pair, using
ADD HL,rp (rp = BC, DE or HL)
Here again, you could double the contents of HL by adding it to itself.
As you might expect, most of these instructions, but not quite all of them, have similar SUBtraction instructions of the form:
SUB A,17
which subtracts 17 from the contents of the A register. All of these instructions are shown in figure nine.
When a value exceeds the range of a register, we said that the ninth bit is lost: however, that is not quite true. In the first article, we referred to an F register, but we have not yet used it. The F register is a special register which holds YES/NO values, called Flags, which we may find useful.
So, when a result wraps around a bit - binary digit - in the F register is set to one. That bit is known as the carry flag, and can be used for operations on large numbers - that is, greater than 65535 - but we will leave that for another article, and instead cover something more interesting.
The Z80 instruction set does not have any multiplication or division instructions, so those have to be simulated using repeated additions or subtractions. There are a few 'shift' instructions to make life a little easier.
If we were to shift all the bits in a register to the left, then we would in effect multiply the contents by two. So, if we take the binary number
00110100b = 52 ((1*4) + (1*16) + (1*32))
and shift all the bits to the left, we get
01101000b = 104 ((1*8) + (1*32) + (1*64))
We can do that in Z80 using the instruction
SLA reg (reg = A,B,C,D,E,H or L)
SLA stands for Shift Left Arithmetic - not a particularly memorable mnemonic, I'm afraid. If a '1' - that is, a set bit - is shifted out of the top position, then it is lost - to the carry flag - and a zero is always shifted into the lowest bit. The only other left- shift instruction is
SLA (HL)
which shifts the contents of the location addressed hv the HL register pair.
We can do exactly the opposite of this using the SRL - Shift Right Logical - instructions. Just to confuse matters further, there are similar SRA - Shift Right Arithmetic - instructions. Those are almost the same as SRL, hut instead of moving a zero into the top bit, that bit remains unchanged. Figure two may explain that better and all the instructions are shown in figure nine.
So what can we use those instructions or? Well, they have obvious uses for multiplication and division, but the assembly code in figure three shows a more interesting application.
All the characters which you see in the display file are held in a series of bytes. Those start at location 4000h - h = hex, 16384 in decimal - and continue to location 57FFh, or for 6144 locations. Each character in this display area is made up from eight bytes.
Figure four shows the arrangement for the letter A. If we were to shift all the bytes in the display file to the right, then all the characters would be displaced by one bit. If we did that eight times then the characters would slowly disappear!
A Basic program to perform this professional-looking screen clear is shown in figure five. Try this out first. When you run it, you may be forgiven for wondering why nothing is happening. The screen is in fact clearing, but very, very slowly.
Now type in the program in figure six which loads the equivalent machine code routine. If you typed in the Attribute Fill routine last month, then you will he able to modify it, because the data - line 2000 onwards - is all that has changed. This program works quite a bit faster.
If we go hack to the assembly code - figure three - we can see how the program works. The B register is loaded with the value eight, because we want to shift the screen to the right eight times to clear it. The main loop then starts by loading the HL register pair with the first screen location, and the DE register pair with the number of bytes on the screen which need to be changed.
The loop from label SHIFT is then executed 6144 times. This loop performs the necessary SRL instruction, then increments the HL register, so that HL eventually moves through the entire screen. Do not worry about how the loops work just at the moment - we'll be looking at those next month.
You can easily include the routine in your own programs. Not only does the screen clear in an unusual manner, but none of the attributes are changed. If you add the lines given in figure seven to the program, which just colour each character square, you will see that the colours are left unchanged. You could subsequently set those using PAPER and CLS, or even use last month's Attribute Fill routine.
The routine is easily modified to perform slightly different functions, too. Change the data value "CB3E" (line 2010) to "CB26". That is the code for "SLA (HL)", which will perform a similar shift, but to the left.
Z80 has a couple of interesting instructions which will rotate bytes. They work in a similar way to the shift instructions, but instead of a zero bit being shifted into the byte, the 'lost bit' is used - see figure eight. Try changing the "CB3E" data to "CB06" or "CB0E", and change line 80 to "GO TO 70", and see what happens.
Next month we will see how the loops which we have been using are formed, then use that knowledge to implement a digital counter on the screen, where the digits rotate properly into their correct positions, just like a real digital clock.
|
|
|
|
|
|
|
|
|
Previous article in series (issue 38)
Next article in series (issue 40)