.........1.........2.........3.........4.........5.........6.........7.........8

FIRST STEPS IN MACHINE CODE
An introduction to Z80 machine code by David Nowotnik
part 4 of 6 - Bits and Stacks
from ZX Computing Jun/Jul'85
(incorrectly numbered part 3 in the magazine)


Ever wondered how a computer remembers where to turn to
after completing a subroutine? All will be revealed in this,
the fourth part of my machine code series. And, we will see
lots of ways in which the smallest unit of memory, the bit,
can be utilised to produce interesting machine code
routines. But first, the stack!


The stack

So far, we've dealt with many instructions to transfer bytes
from one place to another - mainly from one register to
another, and between memory and the registers. There is a
special store of bytes of information in RAM called 'the
stack'. which has its own set of machine code instructions.
The curious thing about the stack is that you usually don't
have to worry about where in RAM it is stored; the processor
does this for you automatically. You can change the position
of the stack if you want to (with special machine code
instructions), but it is usual to leave it where the
processor puts it after power-up, which is just below
RAMTOP.

What is 'the stack'? It's a 'pile' of bytes starting, as I
said, just below RAMTOP, and building in a downward
direction in RAM. The purpose of the stack is to have a
temporary store of information (bytes) which you can dump
there with a simple one byte instruction, then remove just
as simply. It works something like this:

There is a special two byte register in the Z80 called SP,
the stack pointer, which contains the first vacant address
in the stack. If you want to store some information
temporarily then you use the instruction PUSH to place that
information (bytes) onto the stack. You can only PUSH onto
the stack the value in a pair of registers; for example,
PUSH HL, PUSH DE, PUSH BC, and PUSH AF (the 'A' register and
the Flag register combined) are the only PUSH instructions
permitted. When you PUSH values onto the stack, the SP
register is decremented by two, so that it contains the new
address of the next free position in the stack. The stack is
built up by moving DOWN in memory.

To remove a value from the stack, the instruction POP is
used, and the value 'POPed' from the stack can be placed in
one of the register pairs. As part of the POP instruction,
the SP register is twice incremented, to show once more the
address of the next free place on the stack. All the opcodes
for the PUSH and POP instructions are shown in table 1. All
are one byte instructions.

The stack is designed to make life easier for programmers.
For example, if you want to preserve the value in one of the
registers while you use it for something else, simply PUSH
the value onto the stack, then POP it off when you need it.
There will be examples of this later on. To transfer the
values from one register pair to another all you have to do
is to PUSH one register pair value into the stack, then POP
it into the other register pair. I demonstrated this in the
last issue, although I didn't explain how it worked. The
machine code routine in the demonstration of flags program
(Fig.1 in part 3) had as consecutive instructions PUSH AF,
POP DE. This placed the values A and F onto the stack, then
placed them in D and E, respectively. This enabled the F
register to be copied into the E register, from where it can
be more closely examined.

You have to be quite careful how you use the stack. It
operates by a last-on, first-off principle, so you must get
the order on and off just right. The stack is also used by
the Z80 processor outside of your control. Here's the answer
to the subroutine question!

When you call a subroutine, either in BASIC, or a machine
code subroutine, the return address is dumped onto the
stack. If you call several subroutines, the return addresses
are placed onto the stack in the correct order, and you'll
always return to the right place in the program after each
RETURN instruction because of the last on, first off
principle of the stack. It is important therefore, to make
sure that the number of POP instructions within a subroutine
balances the number of PUSH instructions (and not to POP a
value off the stack before one is PUSHed there), otherwise
you could end up removing a return address, and causing
yourself some real headaches.


Working with Bits

There are far more machine code instructions that deal with
bits than there are ones which manipulate bytes. So, it
won't be of surprise to you that I won't be covering all of
them this time, and I'll be saving some for the next part of
this series, However, having said that, there is a very
large number of instructions which can all be grouped into a
relatively small number of categories. But before we start
to examine some of these, it might be useful to re-examine
what a bit just happens to be.

Every byte of memory or register in the Z80 processor is
made up of 8 bits. Each bit is, effectively, an electrical
switch; it has two states, on or off, which can be
represented by the values 1 or 0. There are 256 possible
combinations of ones and zeros in the eight bits that make
up a byte, hence the value range that a byte can hold is 0
to 255. In fig.1 there is a BASIC program for both ZX81 and
Spectrum which demonstrates how the values of bits are
combined to make up the value of a byte. When you RUN the
program, the first thing you have to do is enter a decimal
value. The binary representation of that value (ie. the way
it is held as ones and zeros in a byte) is shown on the
screen. These bits are numbered 0 to 7 from right to left.
Then watch the screen while you get a display of how to
calculate the decimal value of a byte. Every time there is a
'1' in a bit, then the value of that bit is added to the
total. See if you can work out the relationship between a
bit value and its number (0 to 7). If you want to slow down
the display, then increase the size of the loop in line
1000.

With the knowledge that each bit has the effective value of
one or zero, it may not come as a surprise to learn that the
simplest of machine code bit operations involves setting the
value of a specified bit to 1, or 0, or testing whether a
bit's value is either 1 or 0. You can SET (bit=1) or RESET
(bit=0) values, or test them with the BIT instruction, for
any bit in the main registers, A,B,C,D,E,H,L, or a bit in a
byte of memory addressed by HL. If you work out all the
combinations, then there are 64 SET instructions, 64 for
RESET, and 64 for BIT. All are shown in Table 2. The SET,
RESET and BIT instructions are all two bytes long, and all
have as the first byte of the instruction, the hex value CB.

All the assembly language instructions for BIT, SET, and
RESET require two arguments to complete the instruction; the
first is the identifying number of the bit (0 to 7), and the
second is the register, or byte (with (HL)) identifier. For
example, SET 5,E places the value 1 in bit 5 of the E
register, and RESET 1,L places the value 0 in bit 1 of the L
register.

The BIT instruction tests the value of the specified bit,
and places the result in the zero flag. If the bit value is
0, then 1 is placed in the Z flag. The Z flag can then be
tested, as described in the last issue. For example, BIT
3,(HL) tests the value of bit 3 in a byte of memory
identified by the value in HL.

Another group of machine code instructions which work on
bits have the opcodes AND, OR, and XOR. The first two will
be familiar to BASIC programmers; the machine code and BASIC
instructions of AND and OR are related, although that may be
difficult to understand while I explain how they work!

AND, OR, and XOR carry out bit-by-bit comparisons of two
bytes. One byte must be in the 'A' register, the other byte
must exist in any register (including 'A'), or a byte of
memory addressed by HL, or a byte of data stored immediately
after the operand in the machine code routine. The result of
this comparison is stored in the 'A' register, replacing the
previous value there. The result will effect the values of
the sign, zero, and parity flags. Therefore, AND, OR and XOR
are very similar to arithmetic machine code operations,
except that their function is described as 'logic' rather
than arithmetical.

Fig.2 shows a number of what are called 'truth' tables.
These show the result of comparing two bits by one of the
'logic' operations, AND, OR, or XOR. For example, if you AND
a bit value 1 with a bit of value 0, then the result is 0.
OR the same two values, and the result will be 1; XORing 1
and 0 also give you 1. In fact, OR and XOR are very similar,
except that 1 OR 1 = 1, whereas 1 XOR 1 = 0.

How these 'truth' tables work in practice is also shown in
Fig.2. Work your way through the three logic 'sums'
comparing each pair of bits with the corresponding truth
table. Then try the three 'sums' which do not have an
answer. The answer does appear at the end of this article,
together with the decimal equivalent of the binary answer.
You should be able to work out for yourself the decimal
value - if you've practised using the program in Fig.1. You
may well be asking by now "what is the use of these 'logical
operators'?" There will be some examples of them later on,
but, in summary:

AND is used mainly for masking 'bits' within a byte. Say you
wanted to reset bits 0 to 3 (i.e. make them all 0). You
could use the RESET command four times, but it is far easier
to use the machine code command AND 11110000. As bits 0 to 3
in the operand are all zero, then (from the truth tables)
the bits 0 to 3 in the result must also be zero.

OR - In a similar manner to AND, the instruction OR will set
a block of bits so that they all have the value '1'. For
example, OR 00001111 ensures that bits 0 to 3 will all be 1
in the result.

XOR allows comparisons of bits such that, if they are the
same, then the result is 0, and if they are different, the
resultant bit value is 1. When you PRINT OVER 1 on the
Spectrum, you are, in fact using the XOR instruction. Think
of a pixel in the INK colour as '1' and a pixel in the PAPER
colour as '0' (which they are), and try a few PRINT OVER 1s
to see the effect. AND, OR, and XOR opcodes appear in table
3.

In the style of previous parts of this series, we'll now
move onto some machine specific examples; ZX81 first,
followed by examples for the Spectrum. You should see
several of the items of theory covered in this, and previous
parts, hopefully helping you to clarify the theory. The ZX81
examples contain more on PUSH and POP, whereas the ones for
the Spectrum have more on the logical operators, so it's
advised that you read through both sets of examples.


ZX81 Examples

Before we begin with the ZX81 examples; a word of warning
about the stack on the '81. As I mentioned earlier, the
stack appears just below RAMTOP, ie. RAMTOP as set up by the
computer on power-up. If you lower RAMTOP, for example, to
make space for a machine code routine, then you could be
encroaching into the stack area. POKEing anything into the
stack area by mistake is a certain recipe for disaster. The
safest way to create space for machine code at the top of
RAM is to use NEW after lowering RAMTOP. This will relocate
the stack below the new RAMTOP, but, of course, wipe out any
BASIC program, such as a machine code loader.

To avoid any danger of corrupting the stack, the examples in
this issue use another method of saving machine code on the
ZX81; in a REM line. The method was introduced in the last
part, but wasn't fully explained at that time. The position
of a BASIC program is fixed in the ZX81'5 memory, starting
at address 16509. If the first line is a REM statement, then
it (like all other REM lines) is ignored by the BASIC
interpreter, so you can put anything you like into it,
including machine code bytes. And that is what the hex
loader in fig.3 carries out. The first byte after the REM
token in line 10 will always be address 16514 (as long as no
other line precedes 10), so you can replace the 'dummy' full
stops in the REM line by machine code bytes. Line 60 checks
that you are overwriting a full stop, and not another
character, then the machine code byte is POKEd in. Once in
the REM line the machine code can be stored with the rest of
the BASIC program. But beware; if you LIST the program, then
you might corrupt the display file with 'unprintable'
machine code values. Better to LIST 20, so that line 10 does
not appear on the screen. First of all, enter the main
listing in Fig.3, then add whichever line 30 is appropriate
for the routine you want to try.

The two machine code examples in Fig.3 produce effects on
the display file, so you'll see straight away if they are
working correctly. The first causes the screen to 'rotate';
i.e. it is like a normal SCROLL except that the line which
disappears off the edge of the screen reappears on the other
side. The rotate routine works on all 24 rows of the screen,
so press any key a few times to appreciate the effect. The
second routine converts the entire screen to a white on
black display - instantly!

As before, use a 16K (or larger) RAM pack with these
examples, otherwise they may not work.


Spectrum Examples

The two Spectrum examples use the same decimal loader as
used in Part 3. The CLEAR instruction lowers RAMTOP and
moves the stack to below the new RAMTOP, so giving you an
area of RAM which is quite safe to use. Type out the loader,
then add the DATA lines appropriate to the routine you want
to try.

The first routine scans the display file, and inverts every
bit (i.e. changes 1 to 0, and 0 to 1), so reversing INK and
PAPER. The important instruction for this is XOR 255. The
second routine scans the attribute file and 'toggles' the
flash bit (that is, turns FLASH on if it finds the bit off,
and vice versa). So, from a non-flashing screen, you should
get the entire screen flashing. Try it!

Both Spectrum examples display one way of overcoming a major
error in the Z80. Surprisingly, when using DEC on a two byte
register, it doesn't reset the zero flag when the double
register holds a value of zero. So, when using, say, BC as a
counter, JR NZ after DEC BC will not work when BC becomes
zero. It's a common mistake amongst beginners to machine
code to assume (naturally) that it does. The way to overcome
this is shown in the examples. Having carried out the DEC
BC, the values in B and C are (effectively) ORed (after the
value in B is transferred to the A register). Only when both
B and C contain zero will the result of the OR operation be
zero. The zero flag is reset when the result of a logical
operation is zero, so the flag can be tested after the OR
operation.

The final items for this part are the results of the tests.
These are as follows: AND: 00010001 (17); OR: 11101110
(238); XOR: 10100110 (166). If you don't agree with these
results, then try again, working through the program in
Fig.1 and the examples in Fig.2.


- Table 1. Opcodes for PUSH and POP instructions  - - - - - - - - - - - - -

Registers:      HL  DE  BC  AF

   PUSH         E5  D5  C5  F5
   POP          E1  D1  C1  F1


- Table 2. Opcodes for the SET/RESET/BIT instructions - - - - - - - - - - -

SET
     Bit:       0   1   2   3   4   5   6   7

Register:   A   C7  CF  D7  DF  E7  EF  F7  FF
            B   C0  C8  D0  D8  E0  E8  F0  F8
            C   C1  C9  D1  D9  E1  E9  F1  F9
            D   C2  CA  D2  DA  E2  EA  F2  FA
            E   C3  CB  D3  DB  E3  EB  F3  FB
            H   C4  CC  D4  DC  E4  EC  F4  FC
            L   C5  CD  D5  DD  E5  ED  F5  FD
           (HL) C6  CE  D6  DE  E6  EE  F6  FE

RESET
     Bit:       0   1   2   3   4   5   6   7

Register:   A   87  8F  97  9F  A7  AF  B7  BF
            B   80  88  90  98  A0  A8  B0  B8
            C   81  89  91  99  A1  A9  B1  B9
            D   82  8A  92  9A  A2  AA  B2  BA
            E   83  8B  93  9B  A3  AB  B3  BB
            H   84  8C  94  9C  A4  AC  B4  BC
            L   85  8D  95  9D  A5  AD  B5  BD
           (HL) 86  8E  96  9E  A6  AE  B6  BE

BIT
     Bit:       0   1   2   3   4   5   6   7

Register:   A   47  4F  57  5F  67  6F  77  7F
            B   40  48  50  58  60  68  70  78
            C   41  49  51  59  61  69  71  79
            D   42  4A  52  5A  62  6A  72  7A
            E   43  4B  53  5B  63  6B  73  7B
            H   44  4C  54  5C  64  6C  74  7C
            L   45  4D  55  5D  65  6D  75  7D
           (HL) 46  4E  56  5E  66  6E  76  7E

NOTE: All SET/RESET/BIT opcodes are preceded by CB.


- Table 3. AND/OR/XOR opcodes - - - - - - - - - - - - - - - - - - - - - - -

Register/byte:  A   B   C   D   E   H   L   (HL)n*

          AND:  A7  A0  A1  A2  A3  A4  A5  A6  E6
           OR:  B7  B0  B1  B2  B3  B4  B5  B6  F6
          XOR:  AF  A8  A9  AA  AB  AC  AD  AE  EE

* - n refers to an operand appearing immediately after the
    opcode for AND, OR or XOR in the machine code routine.


- Fig.1. Binary Demonstration Program - - - - - - - - - - - - - - - - - - -

  10 REM BINARY DEMONSTRATION
  20 REM BY DAVID NOWOTNIK
  30 REM MARCH, 1985
  40 REM
 100 CLS
 110 PRINT AT 21,0;"ENTER A VALUE (0-255) "
 120 INPUT a
 130 IF a<0 OR a>255 THEN GO TO 120
 140 LET a=INT a
 150 LET b$=""
 160 LET z=a
 170 IF z=0 THEN GO TO 250
 180 LET x=z/2
 190 IF x=INT x THEN LET b$="0"+b$
 200 IF x<>INT x THEN LET b$="1"+b$
 210 LET z=INT x
 220 GO TO 170
 250 PRINT AT 4,2;"NUMBER= ";a
 260 LET b$="00000000"+b$
 270 LET l=LEN b$
 280 LET b$=b$(l-7 TO)
 300 PRINT AT 8,2;"BINARY= ";b$
 400 PRINT AT 21,0;"NOW CONVERT BINARY TO DECIMAL"
 410 LET s=0
 420 PRINT AT 12,20;"TOTAL= ";s
 500 FOR i=0 TO 7
 520 GO SUB 1000
 530 PRINT AT 7,17-i;"V "
 540 PRINT AT 10,2;"BIT VALUE = ";2^i
 550 GO SUB 1000
 560 LET t=VAL b$(8-i)*2^i
 570 PRINT AT 12,5;b$(8-i);" X ";2^i;" = ";t;"  "
 580 LET s=s+t
 590 GO SUB 1000
 600 PRINT AT 12,28;s
 610 GO SUB 1000
 620 NEXT i
 630 PRINT AT 21,0;"  PRESS ANY KEY TO CONTINUE      "
 640 IF INKEY$="" THEN GO TO 640
 650 RUN
 700 REM
1000 FOR j=1 TO 200
1010 NEXT j
1020 RETURN


- Fig.2. Truth Tables, and examples of AND, OR and XOR  - - - - - - - - - -

a) Truth Tables

AND Truth Table:
    |        Bit 1: | 0 | 1 |
     ----------------------- 
    | Bit 2:      0 | 0 | 0 |    0 AND 0 = 0
     -----------------------     0 AND 1 = 0
    |             1 | 0 | 1 |    1 AND 1 = 1
     -----------------------

OR Truth Table:
    |        Bit 1: | 0 | 1 |
     ----------------------- 
    | Bit 2:      0 | 0 | 1 |    0 OR 0 = 0
     -----------------------     0 OR 1 = 1
    |             1 | 1 | 1 |    1 OR 1 = 1
     -----------------------

XOR Truth Table:
    |        Bit 1: | 0 | 1 |
     ----------------------- 
    | Bit 2:      0 | 0 | 1 |    0 XOR 0 = 0
     -----------------------     0 XOR 1 = 1
    |             1 | 1 | 0 |    1 XOR 1 = 0
     -----------------------

b) Example Logical Operations

       AND                OR                 XOR
.   00110101 = 43      01001000 = 72      11100000 = 224
.   10100100 = 174     00101001 = 41      01010001 = 81
    --------           --------           --------
.   00100100 = 36      01101001 = 105     10110001 = 177

c) Examples to try for yourself

       AND                OR                 XOR
.   01010101           10100110           11001010
.   00110011           11001100           01101100


- Fig.3. ZX81 Machine code examples - - - - - - - - - - - - - - - - - - - -

  10 REM ............................................
  20 LET X=16514
  40 FOR I=1 TO LEN A$/2
  50 LET J=16*(CODE A$-28)+CODE A$(2)-28
  60 IF PEEK X=27 THEN POKE X,J
  70 LET X=X+1
  80 LET A$=A$(3 TO)
  90 NEXT I
 100 CLS
 110 FOR I=1 TO 10
 120 PRINT "1234567890ABCDEFGHI"
 130 PRINT "ABCDEFGHIJKLMNOPQRS"
 140 NEXT I
 150 IF INKEY$="" THEN GOTO 150
 160 RAND USR 16514
 170 GOTO 150

---

a) First machine code routine:
  30 LET A$="113C402A0C40012100C5D5C5E5EDB0E1545DC10901F702EDB0E1C1EDB0C9"

Assembly language listing:
      LD DE,16444    113C40    DE=Printer buffer address
      LD HL,(16396)  2A0C40    HL=Display file start
      LD BC,33       012100    Length of character row
      PUSH BC        C5        Store for future use
      PUSH DE        D5
      PUSH BC        C5
      PUSH HL        E5
      LDIR           EDB0      Store row in printer
      POP HL         E1          buffer
      LD D,H         54        DE has first screen
      LD E,L         5D          address
      POP BC         C1        Put 33 back into BC
      ADD HL,BC      09        HL=HL+BC
      LD BC,759      01F702    Chars. for block move
      LDIR           EDB0      Block move for SCROLL
      POP HL         E1        Printer buffer address
      POP BC         C1        BC=33
      LDIR           EDB0      Replace bottom line
      RET            C9        Return to BASIC

---

b) Second machine code routine:
  30 LET A$="2A0C400618C50620237EF6807710F923C110F2C9"

Assembly language listing:
      LD HL,(16396)  2A0C40    Load HL with D_FILE
      LD B,24        0618      B=Number of rows
LOOP1 PUSH BC        C5        Save row counter
      LD BC,32       0620      Number of characters/row
LOOP2 INC HL         23        Get next character
      LD A,(HL)      7E        Load with screen character
***   OR 128         F680      Convert to white on black
      LD (HL),A      77        Place in display file
      DJNZ LOOP2     10F9      Repeat to row end
      INC HL         23        Jump over Newline
      POP BC         C1        Recover the row counter
      DJNZ LOOP1     10F2      Do again, until all done
      RET            C9        Return to BASIC

*** This routine will always give you a white on black
display. You can change it quite simply to interconvert
black on white and white on black bh changing the line
marked with three asterisks to:
      ADD 128        C680      Convert to white on black


- Fig.4. Spectrum machine code examples - - - - - - - - - - - - - - - - - -

  10 CLEAR 24999
  20 LET i=25000
  30 READ x: IF x=-1 THEN GO TO 200
  40 POKE i,x: LET i=i+1
  50 GO TO 30
 190 DATA -1
 200 CLS
 210 FOR i=1 TO 10
 220 PRINT "abcdefghijklmnopqrstuvwxyz"
 230 PRINT "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 240 NEXT i
 250 RANDOMIZE USR 25000
 260 IF INKEY$="" THEN GO TO 260
 270 GO TO 200

---

a) First machine code example:
 100 DATA 33,0,64,1,0,24,126
 110 DATA 238,255,119,35,11,120
 120 DATA 177,32,246,201

Assembly language listing:
      LD HL,16384    210040    HL=Display file start
      LD BC,6144     010018    Bytes in display file
LOOP  LD A,(HL)      7E        Transfer bytes to A
      XOR 255        EEFF      Invert bits
      LD (HL),A      77        Put byte back
      INC HL         23        Next byte
      DEC BC         0B        Decrement counter
      LD A,B         78        Test if 0
      OR C           B1
      JR NZ,LOOP     20F6      If not, repeat
      RET            C9        Else return to BASIC

---

b) Second machine code example:
 100 DATA 33,0,88,1,0,3,126,198,128
 110 DATA 119,35,11,120,177,32,246,201

Assembly language listing:
      LD HL,22528    210058    Start of attributes
      LD BC,768      010003    Number of attribute bytes
LOOP  LD A,(HL)      7E        Load A with attribute
      ADD 128        C680      Toggle on/off FLASH
      LD (HL),A      77        Return to byte
      INC HL         23        Next byte
      DEC BC         0B        Decrement counter
      LD A,B         78        Test if 0
      OR C           B1
      JR NZ,LOOP     20F6      If not, repeat
      RET            C9        Else return to BASIC


--
Another Fine Product transcribed by:
Jim Grimwood (jimg@globalnet.co.uk), Weardale, England
--
