Hit Squad Issue 42 Contents Adventure Helpline

Machine Code



Accumulating logic in the heart of the machine

Marcus Jeffery introduces more marvels of machine code programming

One set of extremely useful instructions in Z80 machine code is known as 'logic' instructions. Those are similar to the logic instructions available in Basic, such as AND and OR.

We will look at those, and then develop a set of picture routines which will allow you to hold more than one screen in memory, copy from one to another, exchange them and merge them together.

When writing programs using Basic, most people have used instructions such as

  IF key  "Z" OR key = "X" THEN ...

in their programs. If you have read the section in the User Manual, then you'll also know that all those instructions return TRUE and FALSE values, where

     TRUE = 1
and  FALSE = 0

That means that if we write the following code:

  LET a = 1
  LET b = 0
  PRINT a AND b
  PRINT a OR b
  PRINT NOT a

it will print '0', '1' and '0'. Also, typing

  PRINT a>b
  PRINT b>a

will print '1' and '0', because 'a is greater than b' is true, but not vice versa. In fact, you'll find that any non-zero value is true. Try changing the value of 'a' to a different number in the above code. You may be surprised by some of the results, but if you try something like

  IF 15 THEN PRINT "The number 15 can also represent TRUE"

you will see what we mean.

What is the point of all this? Well, try

  PRINT 99 AND 77

on the Spectrum, and you will get the answer 99. On most computers such as the BBC micro and Commodore 64 - that would give the result 65! The computer came up with that unexpected result by testing the individual bits of the numbers, with each bit representing its own true/false value.

We can see that better by considering the logic in binary:

        1100011 (99)
AND     1001101 (77)
        -------
gives   1000001 (65)

Even on the Spectrum, when you're working in Z80 machine code, ANDs and ORs work in the same way. So if we had the assembly code

  LD A,15 (00001111 in binary)
  OR 60   (00111100)

then the value remaining in the accumulator would be 63 (binary 00111111). If we then had

  AND 170 (10101010)

we would end up with 42 (00101010) in the accumulator - I wonder if that is the way Deep Thought found the ultimate answer in the Hitchhiker's Guide to the Galaxy!

The AND and OR instructions always return a result in the accumulator, so there is no need to specify that in the assembly language instruction. All those instructions are shown in figure one. For each bit in the two bytes which are being logically ANDed or ORed, the following apply:

  bit 1   |   bit 2   | b1 AND b2
----------|-----------|-----------
    0     |     0     |     0
    0     |     1     |     0
    1     |     0     |     0
    1     |     1     |     1
  bit 1   |   bit 2   | b1 OR b2
----------|-----------|-----------
    0     |     0     |     0
    0     |     1     |     1
    1     |     0     |     1
    1     |     1     |     1

There is another instruction available in Z80. That is an exclusive- or instruction, and is represented by XOR. This can be thought of as 'if bit one is true or bit two is true, but not both'. The logic table is as follows:

  bit 1   |   bit 2   | b1 XOR b2
----------|-----------|-----------
    0     |     0     |     0
    0     |     1     |     1
    1     |     0     |     1
    1     |     1     |     0

One special use of the XOR instruction which you may come across is

  XOR A

What's the use of exclusive- ORing the A register with itself? Let us say the accumulator contained the value 19 (00010011 in binary). If we execute the instruction 'XOR A', the following operation is executed:

        00010011
XOR     00010011
        --------
gives   00000000

In other words, it clears the accumulator, whatever it contains.

That might, for instance, be used before some addition or subtraction instructions. You could, of course, use the instruction 'LD A,0', but that requires an extra byte and takes seven clock cycles rather than four for the XOR instruction.

Finally, the NOT operation known in machine code as CPL which stands for 'complement'. That reverses all the bits in the accumulator so '1's become '0' and vice versa.

Now to the picture routines. There are four routines altogether, and you could easily add your own. They all operate on a principle which uses an area of memory as though it were screen memory. Though we cannot see that area of memory, as we do with the real screen, we can still put it to all sorts of uses.

The assembly code is given in figure two, with the Basic loader and application code in figure three. Before typing them in, let us look at one or two ways in which the logic instructions have been used.

The first routine, MERGE - use RANDOMIZE USR 60000 - will combine the present screen picture with another presently in memory. Each screen pixel is either lit or unlit, which is a '1' or '0' respectively in screen memory. So, to combine them, we just need to OR all the bits. Doing that for a couple of diagonal lines in one character is shown in figure four. The logic instruction that does this is

  OR (HL)

which ORs the accumulator - containing a byte from the additional screen in memory - with the corresponding byte from the real, visible screen.

You might like to try changing that instruction to 'XOR (HL)'; change "B6" to "AE" in line 2020 of the Basic loader to get more unusual effects, as shown in figure five. You could also change it to 'AND (HL)' "B6" to "A6", as in figure six.

The other way we have used logic in this routine is the OR C which is used to check for the end of the loop. We are effectively counting down the BC register pair until it reaches zero. Unfortunately, we cannot check a register pair. What we are really doing is jumping to MNEXT if either of B or C is not zero. That could be done by checking B, and jumping if not zero (JR NZ), then checking C and jumping if not zero. However, we can get the same effect by ORing the register values and jumping. That is a fairly standard and accepted method of completing loops where the loop counter exceeds one byte, that is, 255.

We have used the same looping technique in EXCH - use RANDOMIZE USR 60046 - which will exchange the picture in memory with the real screen. Do not worry about the meaning of the EX AF,AF' instructions. They are only used to save and restore the accumulator, and you could just as easily use 'PUSH AF', for the first and 'POP AF', for the second, but the instruction used works faster.

The other two routines are COPY1 (RANDOMIZE USR 60021) which will copy the visible screen to memory, and COPY2 (RANDOMIZE USR 60034 which does the opposite. The LDIR instruction effectively replaces the series of instructions:

LOOP    LD     A,(HL)
        LD     (DE),A
        INC    HL
        INC    DE
        LD     A,B
        OR     C
        JR     NZ,LOOP

- a very useful instruction which we will be considering at a future data.

To use the routines, you have to initially CLEAR room for the invisible screen. In the Basic example, that screen has been placed in the 6144 bytes from location 53850. That is the reason for the CLEAR 53849 instruction. To specify that this is the screen for the routines to use, the first screen location has to be POKEd into the two locations 23278 and 23279. Those are two unused locations in the area used for Spectrum system variables - see pages 127-130 of the Spectrum User Guide. Those locations are very useful, keeping the screen location safely out of the way of any machine code application programs.

The numbers which should be poked into these locations are

      POKE 23728, screen - 256 * INT (screen / 256)
and   POKE 23729, screen / 256

The way the memory has been used in the example program is shown in figure seven. Of course, there's no reason why, by changing the values in locations 23728 and 23729, you should not hold a number of hidden screens, using the routines to carry out operations on all of them.

Figures

Figure 1. New Z80 instruction codes

AND        byte    - AND the contents of the A register with the byte value
AND        reg     - AND the contents of the specified register with the
                     accumulator
AND        (HL)    - AND the contents of the memory location addressed by the
                     HL register pair with the accumulator
OR        byte     - OR the contents of the A register with the byte value
OR        reg      - OR the contents of the specified register with the
                     accumulator
OR        (HL)     - OR the contents of the memory location addressed by the
                     HL register pair with the accumulator
XOR        byte    - Exclusive-OR the contents of the A register with the byte
                     value
XOR        reg     - Exclusive-OR the contents of the specified register with
                     the accumulator
XOR        (HL)    - Exclusive-OR the contents of the memory location addressed
                     by the HL register pair with the accumulator
CPL                - complement the accumulator

Figure 2.

                     ORG   60000
                     LOAD  60000

EA60 210040   MERGE  LD    HL,16384   ;HL = start of screen
EA63 ED5BB05C        LD    DE,(23728) ;DE = memory screen
EA67 010018          LD    BC,6144    ;BC = number of bytes in screen
EA6A 1A       MNEXT  LD    A,(DE)     ;A = memory byte
EA6B B6              OR    (HL)       ;Combine with screen byte
EA6C 77              LD    (HL),A
EA6D 23              INC   HL         ;Move to next byte in
EA6E 13              INC   DE         ;    both screens
EA6F 0B              DEC   BC
EA70 78              LD    A,B
EA71 B1              OR    C
EA72 20F6            JR    NZ,MNEXT   ;Repeat for complete screen
EA74 C9              RET

EA75 210040   COPY1  LD    HL,16384   ;HL = start of screen
EA78 ED5BB05C        LD    DE,(23728) ;DE = memory screen
EA7C 010018          LD    BC,6144    ;BC = number of bytes in screen
EA7F EDB0            LDIR             ;Perform copy (HL) to (DE)
EA81 C9              RET

EA82 2AB05C   COPY2  LD    HL,(23728) ;HL = memory screen
EA85 110040          LD    DE,16384   ;DE = start of screen
EA88 010018          LD    BC,6144    ;BC = number of bytes in screen
EA8B EDB0            LDIR             ;Perform copy (HL) to (DE)
EA8D C9              RET

EA8E 210040   EXCH   LD    HL,16384   ;HL = start of screen
EA91 ED5BB05C        LD    DE,(23728) ;DE = memory screen
EA95 010018          LD    BC,6144    ;BC = number of bytes in screen
EA98 1A       EBYTE  LD    A,(DE)     ;A = memory byte
EA99 08              EX    AF,AF'     ;Save A
EA9A 7E              LD    A,(HL)     ;A = screen byte
EA9B 12              LD    (DE),A     ;Copy into Memory screen
EA9C 08              EX    AF,AF'     ;Now restore A, and copy
EA9D 77              LD    (HL),A     ;    to real screen
EA9E 23              INC   HL
EA9F 13              INC   DE
EAA0 0B              DEC   BC
EAA1 78              LD    A,B
EAA2 B1              OR    C
EAA3 20F3            JR    NZ,EBYTE   ;Loop for complete screen
EAA5 C9              RET

                     END

Workarea = A50C to A66D
ORG  end = EAA6
LOAD end = EAA6

Figure 3.

  10 CLEAR 53849
  20 POKE 23728,90: POKE 23729,210
  30 GO SUB 1000
  40 CLS : PRINT AT 7,9;"Let's call this"
  50 PRINT AT 8,9;"<<<SCREEN ONE>>>"
  60 PAUSE 100
  70 RANDOMIZE USR 60021
  80 CLS : PRINT AT 7,11;"This is then"
  90 PRINT AT 8,9;"<<<SCREEN TWO>>>"
 100 PAUSE 100
 110 RANDOMIZE USR 60046: PAUSE 20
 120 IF INKEY$="" THEN GO TO 110
 130 RANDOMIZE USR 60000
 140 PRINT AT 13,4;"Now we've got both at once!"
 150 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,"210040"
2010 DATA "ED5BB05C","010018"
2020 DATA "1A","B6","77","23"
2030 DATA "13","0B","78","B1"
2040 DATA "20F6","C9"
2050 DATA "210040","ED5BB05C"
2060 DATA "010018","EDB0","C9"
2070 DATA "2AB05C","110040"
2080 DATA "010018","EDB0","C9"
2090 DATA "210040","ED5BB05C"
2100 DATA "010018","1A","08"
2110 DATA "7E","12","08","77"
2120 DATA "23","13","0B","78"
2130 DATA "B1","20F3","C9","*"

Figure 4.


Figure 5.


Figure 6.


Figure 7.


Previous article in series (issue 41)
Next article in series (issue 43)



Hit Squad Issue 42 Contents Adventure Helpline

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