Hardware World Issue 39 Contents Hit Squad

Machine Code



The day of reckoning

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.

Figures

                     ORG   60000
                     LOAD  60000
EA60 21D204          LD    HL,1234    ;HL = 1234
EA63 3663            LD    (HL),99    ;The contents of the location
                                      ;1234 are set to 99
EA65 34              INC   (HL)       ;The contents of location
                                      ;1234 now equal 100
EA66 23              INC   HL         ;HL now contains 1235
EA67 C9              RET
                     END

Figure 1



Figure 2.


                     ORG   60000
                     LOAD  60000
EA60 0608            LD    B,8        ;B counts through 8 shifts
EA62 210040   NEXT   LD    HL,16384   ;HL = start of screen memory
EA65 110018          LD    DE,6144    ;DE = number of screen bytes
EA68 CB3E     SHIFT  SRL   (HL)       ;Shift each screen byte right
EA6A 23              INC   HL         ;Increment to next byte
EA6B 1B              DEC   DE         ;Count the shifted locations
EA6C 7A              LD    A,D
EA6D B3              OR    E          ;Jump to label SHIFT if DE is
EA6E 20F8            JR    NZ,SHIFT   ;still greater than zero
EA70 10F0            DJNZ  NEXT       ;Repeat from NEXT eight times
EA72 C9              RET
                     END

Figure 3



Figure 4


  10 FOR i=1 TO 704
  20 PRINT CHR$ (25*RND+65);
  30 NEXT i
  40 FOR j=1 TO 8
  50 FOR i=16384 TO 16384+6143
  60 POKE i,INT (PEEK i/2)
  70 NEXT i
  80 NEXT j
  90 STOP

Figure 5


  10 CLEAR 59999
  20 GO SUB 1000
  30 CLS
  40 FOR i=1 TO 704
  50 PRINT CHR$ (25*RND+65);
  60 NEXT i
  70 RANDOMIZE USR 60000
  80 STOP
1000 REM HEX LOAD ROUTINE
1010 DEF FN p(x)=CODE h$(x)-48-7*(CODE h$(x)>=65)
1020 LET byte=0
1030 RESTORE 2000
1040 READ start
1050 READ h$
1060 IF h$="*" THEN GO TO 1160
1070 IF LEN h$<>2*INT (LEN h$/2) THEN PRINT "Odd number of hex digits in: ";h$: STOP
1080 FOR i=1 TO LEN h$
1090 IF NOT((h$(i)>="0" AND h$(i)<="9") OR (h$(i)>="A" AND h$(i)<="F")) THEN PRINT "Illegal hex digit: ";h$(i): STOP
1100 NEXT i
1110 FOR i=1 TO LEN h$ STEP 2
1120 POKE start+byte,16*FN p(i)+FN p(i+1)
1130 LET byte=byte+1
1140 NEXT i
1150 GO TO 1050
1160 PRINT "Code entered"
1170 PAUSE 150
1180 RETURN
2000 DATA 60000,"0608","210040"
2010 DATA "110018","CB3E","23"
2020 DATA "1B","7A","B3","20F8"
2030 DATA "10F0","C9","*"

Figure 6


  42 LET p=INT(8*RND)
  44 LET q=INT(8*RND)
  46 IF p=q THEN GO TO 44
  48 PAPER p: INK q

Figure 7



Figure 8


INC        reg       - add one to the specified register
INC        rp        - add one to the register pair
INC        (HL)      - add one to the location addressed by the HL register pair
DEC        reg       - subtract one from register
DEC        rp        - subtract one from register pair
DEC        (HL)      - substract one from the location addressed by the
                       HL register pair
ADD        A,byte    - add the given byte to the contents of the A register
ADD        A,reg     - add the value of the given register to the A register
ADD        A,(HL)    - add the contents of the byte location addressed by the
                       HL register pair to the A register
ADD        HL,rp     - add the value of the specified register pair to the
                       HL register pair
SUB        byte      - subtract the given byte from the A register
SUB        reg       - subtract the contents of the register from the A register
SUB        (HL)      - subtract the contents of the byte location addressed by
                       the HL register pair from the A register

In the following four operations, the bit shifted out of the byte goes to the
Carry Flag, and the bit shifted into the byte is zero.

SLA        reg       - shift the contents of the register left by one bit
SLA        (HL)      - shift the contents of the location addressed by the
                       HL register pair left by one bit
SRL        reg       - shift the contents of the register right by one bit
SRL        (HL)      - shift the contents of the location addressed by the
                       HL register pair right by one bit

Figure 9


Previous article in series (issue 38)
Next article in series (issue 40)



Hardware World Issue 39 Contents Hit Squad

Sinclair User
June 1985
Another Fine Product transcribed by Jim Grimwood at YRUA?