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

FIRST STEPS IN MACHINE CODE
An introduction to Z80 machine code by David Nowotnik
part 6 of 6
from ZX Computing Oct/Nov'85


In this, the final part of our series on machine language
programming for beginners, the remaining Z80 machine code
instructions will be described. These deal with the index
registers, with input and output commands, and with
interrupts. All of these items have been left until now
quite deliberately. The ideas involved in these commands are
quite complex, and the beginners to machine language
programming will probably manage quite happily without them.
However, for completeness, these command sets will be
covered. At the end of the article there will be a list of
recommended books where these complex commands, and all the
Z80 machine code instructions dealt with in the series can
be found. And, in keeping with the general style of this
series, there are some machine code programs which you can
try for yourself. First of all though, let's examine the
index registers.


Index Registers

The Z80 CPU has two 16-bit registers - denoted IX and IY -
which perform as index registers. Unlike all the other
so-called 16-bit registers, IX and IY only work in 16 bits,
and with them you do not have the option of working with one
half of the 'pair', i.e. 8-bits, at one time. IX and IY can
be likened to the HL register pair, and many of the
operations of IX and IY are identical to HL.

Those operations which work on IX and IY in the same way as
HL result from direct addressing. In other words, operations
carried out directly on IX and IY. For example, LD IX,nn and
ADD IY,BC work exactly the same way as their counterparts
with HL (LD HL,nn and ADD HL,BC).

What is special about IX and IY arises from their use in
indirect addressing. Remember in a previous part of this
series, (HL) denoted 'the byte in memory whose address is
the value contained in the HL register pair'. This is
indirect addressing. IX and IY can also be used for indirect
addressing, but their use differs from that of HL. Instead
of simply having (IX) or (IY), the assembly language
mnemonics for indirect addressing with these registers are
(IX+d) and (IY+d). 'd' stands for displacement. It is a one
byte value in the operand (so having a value between 0 and
255) which is added to the value in IX or IY to obtain the
effective address for indirect addressing. The value of this
extra facility may not be immediately obvious, but it is
often used by machine code programmers for sequentially
accessing a block of bytes.

This extra facility offered by the index registers can be
quite powerful, but is only likely to be used by the more
experienced machine code programmer, so we'll deal with it
in theory only. The similarity of IX and IY to HL continues
in the same way the machine code bytes are formed. Take an
example like ADD HL,BC. The machine code opcode for this is
a single byte instruction, 09 hex. To get the corresponding
machine code for IX (ADD IX,BC) you precede the byte 09 with
DD hex, and for IY, the opcode is preceded with FD. This
pattern governs the formation of all IX and IY opcodes;
precede the opcode for the corresponding HL instruction with
DD for IX and FD for IY. ADD IX,BC is DD09; ADD IY,DE is
FD19; PUSH IX is DDE5 (PUSH HL is E5) and POP IY is FDE1
(POP HL is E1). Simple!

The formation of the indirect addressing instructions with
IX and IY is also simple once you know the rules; again, DD
and FD precede the corresponding HL opcode, but you must
remember that there is at least one operand, the
displacement 'd'. For example, DEC (HL) has the opcode 35.
DEC (IX+d) has the opcode DD35, to which the value of 'd'
must be added, to give a three byte instruction. If d is to
be 8, then the three bytes for DEC (IX+8) are DD3508 hex.

These rules get a little more complicated when there is a
further operand. For example. LD (HL),20 (hex) has the
opcode 36 and an operand 20, so the instruction is 3620. For
LD (IY+08),20 the opcode is FD36. Next comes the value of
'd' (08), and finally the operand 20, giving a four byte
instruction FD360820. And, just to confuse you a little more
(help! - Ed's asst.), the rules are yet more complicated for
the formation of indirect addressed instructions of the BIT,
SET, and RESET of IX and IY. Take BIT 6 (HL); the opcode for
this is CB76. BIT 6, (IX+08) is DDCB0876; in other words the
corresponding HL opcode is now split when translated to the
IX or IY opcode by the value of 'd'.

These fiddly rules emphasise the usefulness of assemblers,
which convert assembly language mnemonics to machine code.
It is so easy to forget how the instruction is made up, that
mistakes in manual coding of IX and IY instructions are
common. Indexed addressing with the Z80 CPU is not as
powerful as with other processors, such as the 6502, so if
you don't want to use them, it is usually relatively easy to
just use the other registers instead. One good reason for
avoiding the use of IX and IY is that they are used by the
BASIC interpreters and operating systems of the ZX81 and
Spectrum, so altering their values could cause a crash on
return to BASIC (or with SLOW mode on the ZX81).


In/Out

The IN and OUT instructions enable the Z80 to communicate
with the 'outside world'. So far, the machine code
instructions have only dealt with operations within the CPU
itself, or communication between the CPU and memory. For
effective operation, the Z80 must be able to communicate
with other devices; notably the keyboard, the cassette
interface, and (if you have one) the printer. In this list
of external devices, the TV or monitor isn't always
included. As we have seen before, the screen display
occupies a certain area of RAM, and this is translated to a
screen image through specialised hardware and software
connections.

The hardware of the computer assigns to these external
devices an identifying number, and connects them to the CPU
through links which are termed 'ports'. The IN and OUT
instructions allow values (bytes) to be transferred between
the CPU and these ports. IN transfers a value from the port
to the Z80, while OUT enables the opposite transfer to
occur.

There are two types of IN and OUT instruction, the simplest
are IN A,(C) and OUT A,(C). The brackets around 'C', if you
recall, suggest indirect addressing. In this case it means
that the register 'C' holds the identifying value of the
port. So, IN A,(C), with 'C' holding a value of 254 would
instruct the transfer of a value from port number 254, and
place it in register 'A'. Similarly, OUT (C),A sends the
value in A to port 254. Similar instructions are available
for all the registers, and their opcodes are listed in table
1. They are all two byte opcodes, preceded by ED.

Also two bytes in length are the outer IN and OUT
instructions, but in these cases the opcode is only one
byte. These take the form IN A,(n) and OUT (n),A. Here,
transfer of data is only possible to and from the 'A'
register. The port is identified by the operand which
follows the single byte opcode; DB for IN and D3 for OUT.
Hence the instruction DBFE collects a value from port 254,
and places it in register 'A'. Port 254 is the cassette
interface on both the ZX81 and Spectrum. You could write
your own LOAD and SAVE routines in machine code knowing that
fact; but, if you did want to use these routines in machine
code, it is far easier to use the routines already available
in ROM for performing these functions.

One routine the ZX81 does lack is the VERIFY facility, which
checks that a program on tape matches the program in the
machine (to ensure proper SAVEing of program). This routine
is given as one of the examples in the ZX81 section. It uses
the IN A,(254( command to read a byte from tape. You'll also
notice a command OUT (255),A - this causes the wiggly lines
you see on the screen when loading by 'reflecting' the byte
value read from tape (in 'A') onto the screen, which, in
this case, has been configured as port 255.

There are block IN and block OUT instructions, which are
equivalent to LDIR and LDDR with load, but these are rarely
used.

Finally, let's take a look at interrupts.


Interrupts

Interrupts are a special form of communication for the CPU.
Whenever you are working with the computer, it seems that
the CPU seems to progress through machine code (either
yours, or that in ROM when you are working in BASIC)
continually. However, the CPU is often being interrupted. An
interrupt is caused by a device connected to the CPU which
demands priority over the current work of the CPU. Let's
first take a look at the various types of interrupt, then at
some examples to see the purpose of interrupts.

Although there are other types of interrupt available to the
Z80 CPU, the important ones for the ZX81 and Spectrum are
the non-maskable interrupt (NMI) and maskable interrupt. As
their names suggest, you cannot prevent an NMI, but you can
stop a maskable interrupt.

In all forms of interrupt, a signal is sent to the CPU via
an interrupt line, by a device requesting service. A good
example of an NMI is the screen refresh on the ZX81. To keep
down the cost of the ZX81 , it was designed with the screen
refresh routines in ROM. The screen refresh causes the TV
image to be updated every 1/50th of a second. Every 1/50th
of a second, an NMI is generated (by an internal clock)
which tells the CPU to stop whatever it is doing. With an
NMI, the current value of the program counter (PC) is stored
(so that the current program can continue after the
interrupt), and the value of 66 hex placed in the PC. All
NMI routines start at this address. In the ZX81 , this is
the start of the screen refresh routine. One of the first
actions of this routine is to check whether SLOW or FAST
mode is set. If FAST is set, the NMI is terminated; if SLOW
is set, then the screen is refreshed. As the ZX81 works four
times faster in FAST than in SLOW mode, you can see how long
and complex it is to renew the screen image. The NMI routine
on the Spectrum does little, apart from resetting the
machine (RST 00).

NMIs are of little use to the programmer, as they cannot be
controlled. This is not the case with maskable interrupts.
These interrupts can be masked by the programmer with the
machine code instruction DI (Disable Interrupt) and
permitted with EI (Enable Interrupt). There are three types
of maskable interrupt denoted by mode 0,1, or 2. These are
set by the machine code instructions IM 0, IM 1, and IM 2.
So, whenever a maskable interrupt signal is received by the
CPU, it is only accepted if the interrupt is not masked, and
then the appropriate interrupt mode enacted, depending on
which mode is set by the programmer (or by ROM, when in
BASIC).

Mode 0 has a quite specialist use. It requires that the
device requesting the interrupt places, on the data bus, the
machine code bytes to be interrupted by the CPU. For
example, a ROM or EPROM overlaying the main ROM could be
brought into use by this interrupt.

Mode 1 saves the PC contents on the stack, and places the
value 38 hex in the PC. The address is the start of a ROM
routine, which on both the ZX81 and Spectrum causes the
keyboard to be read. In normal operation, these computers
have interrupt enabled, and mode 1 set, which allows the
keyboard to be scanned for a keypress every 1/50th of a
second. Sometimes, however, it is not convenient for this
continual scanning to take place. The LOAD and SAVE routines
are one example. At certain times in the LOAD and SAVE
sequence, it is vitally important that the listening or
output routines are not interrupted. During this period,
interrupts are disabled. During non-critical periods in the
LOAD or SAVE routine, interrupts are enabled, to allow the
keyboard to be scanned (to check for the passing of BREAK).

Mode 1 is a fixed routine. While it is useful for the
programmer to know what it does, he can do little with it
apart from turning the keyboard scan routine on or off.

Mode 2 provides a lot more flexibility. It makes use of
another register, the I (or interrupt) register. When a mode
2 interrupt is encountered, the PC contents are stored as
with the other interrupts, and PC is filled with two values.
One is supplied by the device calling the interrupt, the
other is in the I register. Together they form a 16-bit
address, which points to another two byte value held in
memory. It is this second address which is placed in PC.
This 'vectored' system allows the user to define where the
interrupt handling routine is, so allowing the user to write
their own interrupt routine, and have, if desired, a table
of address vectors. The programmer can set the value of the
interrupt register with the command LD I,A. This interrupt
is something for the experienced programmer, but is still
rather useful.


Some Good Books

O.K. That's all the theory covered! If the series has given
you an appetite for machine code, then you may well want to
take your studies further. An excellent 'bible' for Z80
machine code programmers is the book by Rodnay Zaks,
"Programming the Z80". This covers all the theory of Z80
programming in great detail, and is an excellent reference
text. If you want something a little bit more machine
specific, then Toni Baker's book "Mastering Machine Code on
your ZX81" and "40 Best Machine Code Routines for the ZX
Spectrum" by John Hardman and Andrew Hewson can both be
recommended. And, of course, ZX Computing frequently carries
some complex machine code articles to get your teeth into!

To finish off, here are two machine specific examples which
incorporate many of the principles covered in the series.
For the ZX81 there is a VERIFY routine; for the Spectrum a
left/right byte SCROLL which includes screen display and
attributes, and a 'wrap-around' effect.


ZX8I 'VERIFY' Routine

VERIFY is a command which was excluded from the ZX81 due to
the lack of space in the ROM, yet it is one for which users
have a great need. LOADing and SAVEing tend to be unreliable
on the ZX81, and there is no convenient way of checking that
a program is successfully saved before pulling the plug, and
losing the lot! However, the following piece of machine code
performs the verify operation. Having saved a program to
tape, the verify routine will check that the routine on tape
is a true record of the program in ROM.

This verify routine, to be honest, is based almost entirely
on the LOAD routine in the ZX81's ROM. The only change is at
the vital point when a byte is read off tape. Instead of
putting that byte into the appropriate place in RAM, it is
compared with the current byte at that address. If there is
a match, then the routine continues to check the next and
subsequent bytes. If there is a mis-match, then the routine
exits with an error message (R/O). If the verification was
successful, with no mis-matches, then the 0/0 message will
be returned at the end of the routine.

The machine code loading routine is shown in fig.1. For
convenience the routine is held in a REM statement (135
characters long), so that it can be saved onto tape. So,
first type in the long REM line (line 10) and the loading
routine fig.1 (a), then type in the hex digits, one at a
time, reading from left to right, given in fig. 1 (b). Then
delete all lines except line 10, and type in the lines given
in fig. 1 (c). These will transfer the machine code to above
RAMTOP when required. Finally, save the REM line and loading
routine onto tape.

To use the verify routine, it must be loaded into your 16K
ZX81 prior to any other program. This is the sequence of
actions. First, lower RAMTOP with the two direct commands:

	POKE 16388,0
	POKE 16389,124

This isolates just enough space at the top of RAM for the
verify routine. Now, lower the stack, by using NEW as a
direct command, LOAD and RUN the verify program; this
installs the machine code above RAMTOP. Finally, NEW the
machine code loader, and start typing in your program.

After you have SAVEd your BASIC program onto tape, and you
want to verify your recording, rewind the tape to the start
of the program in tape, type in the direct command

	RAND USR 31744

Press play on your tape recorder, and NEWLINE on the
computer. The screen will appear as if the program were
being loaded. There is no check of the name of the program
on tape, so be sure to play back the right program (if you
have more than one on the same tape). If you saved the
program with variables, then CLEARed them before verifying,
or changed the variables in any way, then you may get a
verify error. Otherwise, if all is well, you'll get an 0/0
message to tell you that the program has been verified.

The assembly language listing of the verify routine appears
in fig.2.


Spectrum Scrolling Routines

The final Spectrum example in this series is one for special
effects. It allows full screen scrolling, left or right,
moving attributes as well, and incorporates wrap-around.
This effect means that characters which fall off the edge of
the screen re-appear on the other side. If you don't want
the wrap-around effect, you can replace the bytes underlined
in the hex dump of fig. 3. with zeros (00).

So, first type in the hex loader in fig. 3., then RUN it to
install the bytes. The code is fully relocatable (i.e. it
doesn't matter where in RAM you store it, it should always
work), so on a 48K machine, you could load it higher in RAM,
so that not so much memory is wasted.

Once you run the program, the machine code is placed above
RAMTOP, and you can NEW the loader. The direct command:

	RANDOMIZE USR 30000

will cause the whole screen display to move one byte to the
left, while:

	RANDOMIZE USR 30060

causes the display to move to the right. if you want to
incorporate these routines in a BASIC program, then one
suggestion is to have the display move when the appropriate
'arrow' key is pressed; for example:

200 IF INKEY$="5" THEN RANDOMIZE USR 30000
210 IF INKEY$="8" THEN RANDOMIZE USR 30060

The assembly language listing for these routines appears in
fig.4.

Well - that's it! I hope you have enjoyed reading the series
as much as I've enjoyed writing it. You should find machine
code much simpler than you ever expected. Try writing your
own routines, and carry on working out how routines written
by others do work - there will always be routines in ZX
Computing for you to try. Good luck!


- Table 1. IN and OUT opcodes - - - - - - - - - - - - - - - - - - - - - - -

IN A,(C)     ED78            OUT (C),A     ED79
IN B,(C)     ED40            OUT (C),B     ED41
IN C,(C)     ED48            OUT (C),C     ED49
IN D,(C)     ED50            OUT (C),D     ED51
IN E,(C)     ED58            OUT (C),E     ED59
IN H,(C)     ED60            OUT (C),H     ED61
IN L,(C)     ED68            OUT (C),L     ED69

IN A,(nn)    DBnn            OUT (nn),A    D3nn


- Fig.1. ZX81 Verify program  - - - - - - - - - - - - - - - - - - - - - - -

a) Machine code loader routine

  10 REM ..............   (at least 135 full stops)
  20 LET X=16514
  30 INPUT A$
  40 IF A$="S" THEN STOP
  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 GOTO 30

b) Machine code bytes

CD 23 0F 37 11 00 00 CB 12 CB 0A CD 10 7C 18 FB 0E 01 06 00
3E 7F DB FE D3 FF 1F 30 49 17 17 38 28 10 F1 F1 BA D2 7A 7C
62 6B CD 10 7C CB 7A 79 20 03 BE 20 D6 23 17 30 F1 FD 34 15
21 09 40 50 CD 10 7C 00 CD 6C 7C 18 F6 D5 1E 94 06 1A 1D DB
FE 17 CB 7B 7B 38 F5 10 F5 D1 20 04 FE 56 30 B2 3F CB 11 30
AD C9 7A A7 28 BB CF 0C EB 21 7C 40 37 ED 52 30 06 1A B9 28
02 CF 1A 13 2A 14 40 37 ED 52 EB D0 CF 1E

[This is just 134 bytes. JG.]

c) BASIC routine to load machine code above RAMTOP

  20 LET X=16514
  30 FOR I=31744 TO 31878
  40 POKE I,PEEK X
  50 LET X=X+1
  60 NEXT I
  70 NEW


- Fig.2. Assembly language listing of ZX81 Verify - - - - - - - - - - - - -

        CALL 3875      CD230F
        SCF            37
        LD DE,0        110000     Indicates no program name
        RL D           CB12
        RRC D          CB0A
loop    CALL listen    CD107C     Routine to receive signal
        JR loop        18FB         from tape
listen  LC C,1         0E01
        LD B,0         0600
loop1   LD A,127       3E7F
        IN A,(254)     DBFE       Listen to tape
        OUT (255),A    D3FF       'Reflect' signal to screen
        RRA            1F         Bit 0 to Carry
        JR NC,break    3049       Jump if BREAK pressed
        RLA            17         Bit 6 to Carry to check
        RLA            17           if signal found
        JR C,read      3828       Jump to routine to build up a byte
        DJNZ loop1     10F1
        POP AF         F1         Remove return address from the stack
        CP D           BA
loop5   JP NC,endch    D27A7C     Check if done
        LD H,D         62         Transfer DE (=0) to HL
        LD L,E         6B
        CALL listen    CD107C
        BIT 7,C        CB7A       The 'check name' routine
        LD A,C         79           from ROM 'LOAD'; not
        JR NZ,+3       2003         used by this routine
        CP (HL)        BE
        JR NZ,-42      20D6
        INC HL         23
        RLA            17
        JR NC,-15      30F1
        INC (IY+21)    FD3415
        LD HL,16393    210940     First byte off tape is
loop2   LD D,B         50           address 16393
        CALL listen    CD107C
        NOP            00
        CALL sysvar    CD6C7C     Check is system variable
        JR loop2       18F6       Yes - continue listening
read    PUSH DE        D5
loop3   LD E,148       1E94       Set timing loop for tape
        LD B,26        061A
loop4   DEC E          1D
        IN A,(254)     DBFE       Tape read
        RLA            17
        BIT 7,E        CB7B
        LD A,E         7B
        JR C,loop3     38F5
        DJNZ loop4     10F5
        POP DE         D1
        JR NZ,done?    2004       Is the byte complete in
        CP 86          FE56         register E?
        JR NC,listen   30B2       Continue listening ...
done?   CCF            3F
        RL C           CB11
        JR NC,listen   30AD
        RET            C9         Byte complete
break   LD A,D         7A         If break not pressed ...
        AND A          A7
        JR Z,loop5     28BB         carry on listening
        RST 08         CF0C       Otherwise give error 'D'
sysvar  EX DE,HL       EB         Address into DE
        LD HL,16508    217C40     Start address of program area
        SCF            37         Set Carry
        SBC HL,DE      ED52       Is address still in system variables?
        JR NC,end?     3006       If not, jump
        LD A,(DE)      1A         Is the byte in RAM ...
        CP C           B9           the same as on tape?
        JR Z,end?      2802       If so, then jump
        RST 08         CF1A       Otherwise generate the error message 'R'
end?    INC DE         13         Next address
        LD HL,16404    2A1440     HL set to end of variables
        SCF            37
        SBC HL,DE      ED52       Has end been reached?
        EX DE,HL       EB
        RET NC         D0         If not, carry on
        RST 08         CF1E       If yes, give '0' end message


- Fig.3. Spectrum byte scroll routine - BASIC 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
 200 DATA "21FF5701C000C501E1FF097E54"
 210 DATA "5D23011F00EDB02B7701E0FF09C1"
 220 DATA "0B78B120E6011800110058210158"
 230 DATA "1AC5011F00EDB02B77132323C10B"
 240 DATA "78B120EEC9"
 250 DATA "21004001C000C5011F00097E54"
 260 DATA "5D2B011F00EDB8237701200009"
 270 DATA "C10B78B120E601180011FF5A21FE"
 280 DATA "5A1AC5011F00EDB823771B2B2BC1"
 290 DATA "0B78B120EEC9"
 300 DATA "s"


- Fig.4. Assembly language listing of the Spectrum byte scroll routine  - -

(Left)  LD HL,22527    21FF57     Last screen address
        LD BC,192      01C000     Number of lines
loop    PUSH BC        C5         Save this value
        LD BC,-31      01E1FF     Calculate the first
        ADD HL,BC      09           address of the line
        LD A,(HL)      7E         Save byte in A
        LD D,H         54         Put address into DE
        LD E,L         5D
        INC HL         23         Next address
        LD BC,31       011F00     32 columns - 1
        LDIR           EDB0       Scroll left
        DEC HL         2B         Set column to 32
        LD (HL),A      77         Wrap-around
        LD BC,-32      01E0FF     Set HL to start
        ADD HL,BC      09           of the next line
        POP BC         C1         Recover the line counter
        DEC BC         0B         Decrement counter
        LD A,B         78         Check if zero
        OR C           B1
        JR NZ,loop     20E6       Continue if not zero
        LD BC,24       011800     24 attribute lines
        LD DE,22528    110058     1st attribute address
        LD HL,22529    210158     2nd attribute address
loop2   LD A,(DE)      1A         Store attribute
        PUSH BC        C5         Store line counter
        LD BC,31       011F00
        LDIR           EDB0       Scroll left
        DEC HL         2B         Set HL to last byte
        LD (HL),A      77         Wrap-around
        INC DE         13
        INC HL         23         Set HL and DE to
        INC HL         23           the next line
        POP BC         C1         Recover line counter
        DEC BC         0B         Decrement counter
        LD A,B         78         Check if zero
        OR C           B1
        JR NZ,loop2    20EE       Go back if not zero
        RET            C9         Else return to BASIC

The scroll right routine is very similar to the scroll left
routine, so it will not be repeated here.


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