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

FIRST STEPS IN MACHINE CODE
An introduction to Z80 machine code by David Nowotnik
part 2 of 6 - Working with Registers
from ZX Computing Feb/Mar'85


Last time I introduced to you the concepts of the Z80
microprocessor, machine code and assembly language
programming. Using those concepts, I gave a few simple
examples of moving data to and from the Z8O CPU. To remind
you of the internal layout of this CPU, the diagram of the
CPU is shown again this time (fig. 1) [see ZXMC85.GIF], as
I'll be referring to it again as I develop some more of the
ways in which machine code can transfer data from memory to
the internal registers of the CPU. But before we begin with
these aspects of the machine code, I'd like to introduce you
to a different way to deal with numbers when working with
computers. This numeric system is called hexadecimal
(commonly abbreviated to hex).


Hexadecimal

The numeric system we use in every day life is called
decimal. To use decimal, we have 10 characters (0 to 9)
which we combine to define a number. Decimal is easy for us
to learn as we have ten fingers to use in counting.

The computer, however, finds decimal cumbersome as it has
eight 'fingers'; the number of bits in a register or a byte
in memory. When using BASIC, the interpreter in ROM
translates numbers to decimal to make life easier for us.
But in using machine code, it becomes easier to adopt the
computer's numeric system. This is a system which has 16
characters ('two hands' of eight 'fingers') which make up
all numbers, and is called hexadecimal.

Hexadecimal works like this: The numbers 0 to 9 in decimal
are the same in hexadecimal (or hex, for short). 10 decimal
becomes 'A' in hex, eleven is 'B', and so on up to 15, which
is 'F' hex. Sixteen is then '10' hex, and 26 decimal is '1A'
hex. The highest value in hex with just two characters is
'FF', which is 255 in decimal. And that just happens to be
the highest value that can be stored in a byte (or in a
single register). So, every byte value can be defined by a
two character number using hex (e.g. 0A for ten, FA for 250
decimal).

Some of the other advantages of hex will become apparent as
we go through the series. To encourage you to use hex rather
than decimal, I have incorporated a hex to decimal
conversion program (suitable for both ZX81 and Spectrum), in
Fig.2, and, later, two new machine code loaders which use
hex rather than decimal, as was used last time. But, for
now, back to machine code.


More LOADing

I introduced last time the assembly language command of
LOAD. It happens to be the most frequently used machine code
operation of all. There are several variations on LOAD; the
simpler and most frequently used will now be described.

The simplest of all are the series of LOAD commands which
allow the contents of one register to be copied into another
register. The BASIC equivalent is LET B=C (for LD B,C); the
originating register remains unaltered, but the value it
holds (a number between 00 and FF hex) is copied into the
other register. A single opcode carries out this operation;
table 1 displays the opcodes for all possible inter-register
transfers. There is no equivalent instruction to copy the
contents of one register pair into another pair, but this
can be achieved by two single register transfers, e.g.

	LD	H,B
	LD	L,C

to copy BC into HL.

It is, of course, necessary to load values into the
registers from outside the CPU. One way to do this is to
load data from the program. For example, the instruction:

	LD	B,n

will load the B register with a value represented by 'n'. In
machine code, LD B,n appears as a two byte instruction. The
first byte is 06 hex, which is the opcode for LD B,n. The
value in the memory address immediately following this
opcode is the value to be loaded into the B register. This
is known as the operand. For example, if the two bytes
(opcode and operand) were 06FB, then the value FB would be
loaded into the B register. All the other registers can be
loaded from the program in the same way, and all the opcodes
for these instructions appear in table 2. Remember, you must
have a second byte to complete this instruction.

You can, if you wish, load a register pair directly with a
single instruction, rather than use two instructions. From
last time, you will recall that the H and L, B and C, and D
and E registers can be paired such that they effectively can
hold any number between 0 and 65535 (0000 to FFFF hex). C,E,
and L form the low byte of the pair, while B,D, and H are
the high bytes.

Three bytes make up the machine code instruction to load a
register pair directly. The first is the opcode, the second
is the value which goes in the low byte, and the third the
value to go in the high byte. Note that the low byte
precedes the high byte in a two byte number. This is a Z80
convention, and we'll see more of that in a moment. The two
byte load instructions also appear in Table 2.

The direct load instructions are equivalent to, for example,
LET B=5, or LET BC=1225 in BASIC. For more flexibility, you
may wish to load the equivalent of a variable (i.e. the
contents of an address in memory) into one of the registers.
There are a number of ways you can do this.

For reasons which will become apparent later, the A register
is special. For example, it is the register in which all one
byte arithmetic is carried out. So, there are more options
to LOAD A than any other single register. You can load the A
register with the contents of a byte from a specific memory
address with LD A,(nn). 'nn' represents a memory address,
and you may remember from last time that the brackets mean
'the contents of'. LD A,(nn) is a 3 byte instruction in
machine code; the first byte (3A) is the opcode, the second
and third are the low and high byte respectively of the
address of the byte whose value is loaded into A.

Similarly a register pair can be loaded directly from
memory; i.e. LD dd,(nn), where dd represents HL, DE, or BC.
When dd is HL, there is a single opcode (2A), followed by a
two byte operand. To load BC or DE a two byte opcode is
required, followed by the address operand. The address
operand points to the byte whose value is loaded into the
low register of the pair; the next address in memory after
the operand address is loaded into the high register of the
pair.

For yet more flexibility, you can use the HL register pair
to point to an address in memory whose value is loaded into
a register. Thus, LD r,(HL) - where r represents any
register - is an instruction requiring only one byte (the
opcode) which takes the number stored in the HL register as
the address in memory from which a value is copied into the
register. The A register allows the BC and DE register pairs
to perform as pointers as well (e.g. LD A,(BC)).

Whenever it is possible to load a register (or register
pair) from an address in memory, it is also possible to copy
the register value into memory. For example, LD (HL),A will
copy the contents of the A register to an address in memory
indicated by the value of the HL register.

The final LOAD instruction for now allows you to place a
value into RAM without going through one of the registers of
the CPU. That is LD (HL),n. A two byte instruction, the
second being the value which goes into the address indicated
by the value held in the HL register pair.

Well, that was a lot of theory in a short space. If it
wasn't clear, read the section again, and look at Tables 2
and 3, which contain opcodes for all the LOAD instructions
dealt with just now. Several of them will appear in the
examples I give later on, which should also help to clarify
these instructions.


Simple Arithmetic

Computers wouldn't get very far if all you could do was to
transfer bytes of data from one place to another. Much of
the rest of the series will deal with how registers can be
manipulated; we'll start now with some simple arithmetic.
Next issue I'll demonstrate addition and subtraction in
machine code, but if all you want to do is add or subtract
the number one from a register (or register pair) then the
Z80 provides a simple way of doing it.

The instructions INC and DEC will, respectively, add or
subtract one from any register or register pair. All these
instructions are just one byte long (the opcode), and their
values are shown in table 4. As an example, if the value of
the A register is 3A, then INC A will increase it to 3B, and
DEC A will decrease it to 39 hex. If the value of A were FF
(the highest possible) then INC A would convert the value in
A to zero; similarly, DEC A when A is zero would make A hold
the value FF.

On paired registers, INC and DEC work on the combined value
held by the pair of registers. So, beware, INC HL is not the
same as INC H and INC L.

Again, the examples should clarify any doubts about INC and
DEC.


A Simple Loop

With the machine code I have given you so far, it would only
be possible to go to the start of a code (the USR function),
carry out a list of instructions, and, with the RET
instruction, return to BASIC. From BASIC, you will have
learnt the power and utility of FOR ... NEXT loops; it's
possible to do a similar thing in machine code. The simplest
way is with a complex instruction DJNZ n.

The letters stand for Decrement B and Jump if Not Zero. What
it does is to use the B register as a counter, just like the
variable in a FOR ... NEXT loop. When this instruction is
encountered, the B register is decremented, and its value is
tested. If it is not equal to zero, then the Program Counter
(remember that from last time?) will jump to a value
governed by the value of n, the operand to DJNZ (this is a 2
byte instruction). If the value of B is zero, then PC is
incremented in the normal way, such that the next
instruction (immediately following the DJNZ instruction) is
implemented.

How the value of n controls the jump is quite complicated.
It allows you to jump both forward and backwards; this is
called relative jumping (as PC is altered relative to its
current position). Here's how it works:

If the value of n is between 0 and 127, then the program
counter jumps forward by the value of n; i.e. PC=PC+n. But
if the value of n lies between 128 and 255, then the program
counter jumps back according to the sum PC=PC-(256-n). The
most commonly occurring fault in writing machine code is the
miscalculation of a relative jump, and there is little
wonder why one of the great benefits of writing in assembly
language, and having an assembler program translate to
machine code is that the calculation is carried out for you.
If you do calculate relative jumps for yourself, then
remember that the starting point for the sum is the address
of the opcode immediately following the DJNZ instruction.
That's the place you would end up if you had DJNZ 0.

For simple loops in machine code, you will be jumping back
following the DJNZ instruction, and you'll see that in the
examples I will be giving. You'll also notice that every
time a loop with DJNZ is set up, the B register is filled
with an appropriate value, much like the FOR statement in a
FOR ...NEXT loop.

One final opcode for now; you'll see it in some of the
examples - that is NOP. It simply means do nothing. It is
machine code's way of carrying out PAUSE. NOP only slows
down machine code for a fraction of a second, so you'll need
quite a few NOPs (in a loop) to see any effect.

Phew! That was a lot of theory in a small space. Let's try
some examples now as light relief, and hopefully, to clarify
the theory. You'll notice that I have split up examples for
the ZX81 and Spectrum. That's because the differences
between the machines are quite considerable when it comes to
machine code. So, GOTO the appropriate section!


ZX81 Examples

To try the machine code examples in this issue you'll need
the hex loader in fig. 3. Type this in and SAVE it. The
machine code goes in as a string of hex values in line 30,
so substitute the line 30 in examples one to three, then
RUN, followed by the direct command RAND USR 30000 to
activate the routines.

All examples use the display file; that is, you'll see the
effect of the machine code on the screen.

In example 1, you'll get a row of '5's printed on the top
line of the screen. In example 2, you'll get a screen full
of '5's - instantly (try that in BASIC). The final example
will give you a '5' dashing across - don't blink or you'll
miss it.

Space doesn't permit too full an explanation of how these
routines work. Try them out for yourself, look at the
assembly language listing, and see if you can work out what
is happening. You'll get some more clues next issue.

Also, try varying the hex numbers underlined (generally use
a smaller number) and see what happens. [NOTE: There was no
underlining printed with the examples. JG.]


Spectrum Examples

My Spectrum machine code hex loader appears in Fig. 5. First
enter it, then save it. The machine code appears in DATA
statements in line 1000 onwards, so for each example, type
in the DATA lines appropriate for that example. In all cases
leave line 2000 as the last line; this contains a symbol
('s') that informs the program you have come to the end.

From the details given in the assembly language listing you
should be able to work out what is happening. all examples
use the Spectrum's display or attribute file, so the effect
is always visual.

With the appropriate DATA lines in place (in fig 5) RUN the
program, then call the machine code with RANDOMIZE USR
30000, and see what happens. Try experimenting by changing
those numbers which are underlined in Fig 5. Good luck!
[NOTE: There was no underlining printed with the examples.  JG.]


- Figure 2. Hex to Dec conversion program  - - - - - - - - - - - - - - - - -

 10 LET A$="0123456789ABCDEF"
100 INPUT B$
110 IF B$(1)="$" THEN GOTO 300
120 LET Z=VAL B$
130 LET A=Z
140 LET H$=""
150 IF A=0 THEN GOTO 400
160 LET R=INT(A/16)
170 LET S=A-16*R
180 LET H$=A$(S+1)+H$
190 LET A=R
200 GOTO 150
300 LET H$=B$(2 TO)
310 LET Z=0
320 FOR J=1 TO LEN H$
330 FOR I=1 TO 16
340 IF H$(J)=A$(I) THEN GOTO 370
350 NEXT I
360 STOP
370 LET Z=Z+(I-1)*16**(LEN H$-J)
380 NEXT J
400 PRINT Z,H$
410 GO TO 100

This program interconverts decimal and hex numbers. When you run
the program, enter a decimal number, or a hex number with a "$"
sign prefix. This tells the program (line 110) whether the number
is hex or decimal. Both hex and decimal numbers will be printed
on the screen.
NB. In line 370, the double star "**" is "to the power of" on the
ZX81; on the Spectrum, this should be "^".


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Table 1. Opcodes for loading between registers

                LD r,r'

                Load from: (r)

                A   B   C   D   E   H   L
Load to:    A   7F  78  79  7A  7B  7C  7D
            B   47  40  41  42  43  44  45
   (r')     C   4F  48  49  4A  4B  4C  4D
            D   57  50  51  52  53  54  55
            E   5F  58  59  5A  5B  5C  5D
            H   67  60  61  62  63  64  65
            L   6F  68  69  6A  6B  6C  6D


Table 2. Load registers with numbers from the program

                LD r,n          LD dd,nn

                LD A,n  3E      LD BC,nn  01
                LD B,n  06      LD DE,nn  11
                LD C,n  0E      LD HL,nn  21
                LD D,n  16
                LD E,n  1E
                LD H,n  26
                LD L,n  2E


Table 3. Load registers to and from memory

Loading from memory             Loading to memory

LD A,(nn)       3A              LD (nn),A       32
LD A,(BC)       0A              LD (BC),A       02
LD A,(DE)       1A              LD (DE),A       12
LD A,(HL)       7E              LD (HL),A       77

LD B,(HL)       46              LD (HL),B       70
LD C,(HL)       4E              LD (HL),C       71
LD D,(HL)       56              LD (HL),D       72
LD E,(HL)       5E              LD (HL),E       73
LD H,(HL)       66              LD (HL),H       74
LD L,(HL)       6E              LD (HL),L       75

LD BC,(nn)    ED4B              LD (nn),BC    ED43
LD DE,(nn)    ED5B              LD (nn),DE    ED53
LD HL,(nn)      2A              LD (nn),HL      22

                               (LD (HL),n       36)


Table 4. Increment and Decrement registers

        A   B   C   D   E   H   L   BC  DE  HL

INC     3C  04  0C  14  1C  24  2C  03  13  23
DEC     3D  05  0D  15  1D  25  2D  0B  1B  2B


- Figure 3. ZX81 hex loader program - - - - - - - - - - - - - - - - - - - -

10 POKE 16388,47
20 POKE 16389,117
30 LET M$=""
40 LET X=30000
50 FOR C=1 TO LEN M$ STEP 2
60 POKE X,(16*(CODE M$(C)-28)+(CODE M$(C+1)-28))
70 LET X=X+1
80 NEXT C

Example 1.
30 LET M$="2A0C40062023362110FBC9"

Example 2.
30 LET M$="2A0C403E1611947512062023362110FB231A473D1210F2C9"

Example 3.
30 LET M$="2A0C401194753E1E1223362106FF0010FD36001A473D1210F0C9"


- Figure 4. Assembly language listings for ZX81 - - - - - - - - - - - - - -

Example 1.
     LD HL,(16396)   2A0C40   Start of display file
     LD B,32         0620     Loop counter set
LOOP INC HL          23       Next place in display
     LD (HL),33      3621     Put '5' on the screen
     DJNZ LOOP       10FB     Jump back to LOOP
     RET             C9         or return if 32 done.

Example 2.
     LD HL,(16396)   2A0C40   Start of display file
     LD A,22         3E16     Number of rows
     LD DE,30100     119475   Place to store row
     LD (DE),A       12         counter
ONE  LD B,32         0620     Number of columns
TWO  INC HL          23       Next screen position
     LD (HL),33      3621     Put '5' on screen
     DJNZ TWO        10FB     Jump back to TWO
     INC HL          23       Jump over Newline
     LD A,(DE)       1A       Check on row counter
     LD B,A          47
     DEC A           3D
     LD (DE),A       12       Store the row counter
     DJNZ ONE        10F2     Jump back to ONE
     RET             C9         unless 22 rows complete.

Example 3.
     LD HL,(16396)   2A0C40   Start of display file
     LD DE,30100     119475   Column counter
     LD A,30         3E1E     Number of columns
     LD (DE),A       12         stored
ONE  INC HL          23       Next screen address
     LD (HL),33      3621     Place '5' on the screen
     LD B,255        06FF     Delay counter
TWO  NOP             00       Delay loop
     DJNZ TWO        10FD
     LD (HL),0       3600     Erase '5'
     LD A,(DE)       1A       Check on column counter
     LD B,A          47
     DEC A           3D
     LD (DE),A       12       Replace counter value
     DJNZ ONE        10F0     Jump back to ONE
     RET             C9         unless all done.


- Figure 5. Spectrum hex loader - - - - - - - - - - - - - - - - - - - - - -

  10 CLEAR 29999
  20 LET x=30000
  30 READ a$: IF a$="s" THEN STOP
  40 FOR i=1 TO LEN a$ STEP 2
  50 LET y=0
  60 FOR j=0 TO 1
  70 LET r=CODE (a$(i+j))
  80 LET s=r-48-(r>57)*7
  90 LET y=y+16*s*(j=0)+s*(j=1)
 100 NEXT j
 110 POKE x,y: LET x=x+1
 120 NEXT i: GO TO 30

2000 DATA "s"

Example 1.
1000 DATA "2100581194753E1612"
1010 DATA "0620367F2310FB1A473D"
1020 DATA "1210F3C9"

Example 2.
As above, but change line 1000 to:
1000 DATA "2100401194753EC812"

Example 3.
1000 DATA "2100581194753E0612"
1010 DATA "3E003295753A95750680"
1020 DATA "772310FC06083C10FD"
1030 DATA "3295751A473D1210E9C9"


- Figure 6. Assembly language listings for Spectrum - - - - - - - - - - - -

Example 1.
     LD HL,22528     210058   Start of the attributes
     LD DE,30100     119475   Storage byte
     LD A,22         3E16     Loop counter
ONE  LD (DE),A       12       Stored at 30100
     LD B,32         0620     Column counter
TWO  LD (HL),127     367F     Attribute value
     INC HL          23         placed in row
     DJNZ TWO        10FB
     LD A,(DE)       1A       Recover loop counter
     LD B,A          47       Copy in B
     DEC A           3D       Loop counter minus one
     LD (DE),A       12       Replace loop counter
     DJNZ ONE        10F3     Jump back to ONE
     RET             C9         unless all done.

Example 2.
This is the same as 1 except for the first three lines:
     LD HL,16384     210040   Start of the display file
     LD DE,30100     119475   Storage byte
     LD A,200        3EC8     Loop counter

Example 3.
     LD HL,22528     210058   Start of attributes
     LD DE,30100     119475   Storage byte
     LD A,6          3E06     First loop counter
     LD (DE),A       12         stored
     LD A,0          3E00     Attribute value
     LD (30101),A    329575     stored
ONE  LD A,(30101)    3A9575   Recover attribute value
     LD B,128        0680     Loop counter
TWO  LD (HL),A       77       Place attribute in file
     INC HL          23         next position
     DJNZ TWO        10FC       until 128 done
     LD B,8          0608     Add eight
TRE  INC A           3C         to attribute value
     DJNZ,TRE        10FD
     LD (30101),A    329575   Store new value
     LD A,(DE)       1A       Recover counter
     LD B,A          47       Copy into B
     DEC A           3D       Counter minus one
     LD (DE),A       12       Store new counter value
     DJNZ ONE        10E9     Jump back to ONE
     RET             C9         unless B=0


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