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

Mastering Machine Code on Your Spectrum
part 3 of 8 - from ZX Computing Feb/Mar'83

Continuing her series on machine code, Toni Baker, author of
'Mastering Machine Code on your ZX81', shows you how to use
your programming skills to write a small, but efficient,
program.


In this article I shall introduce you to a few simple
machine code instructions and use them to write a short but
very, very impressive program.

The first two of these instructions are CALL and RET. RET
you've seen before - it is used to end a machine code
program in order to RETurn to BASIC, but it also has another
use. CALL and RET in machine code are quite similar to GOSUB
and RETURN in BASIC. CALL is used to enter a subroutine, and
RET will exit the subroutine and move control back to the
instruction immediately after the CALL instruction. Instead
of a line number you have an address, so CALL pq means
"GOSUB address pq" - not "GOSUB line pq". In machine code,
remember, there are no line numbers.

And now for some more exciting machine code instructions: IN
and OUT. Both of these have equivalents in Spectrum BASIC
(although not in ZX81 BASIC) but most people have absolutely
no idea how to use them. In BASIC, they are in a sense
similar to PEEK and POKE, and so in machine code there is a
certain similarity between IN and OUT, and LD r,(pq) and LD
(pq),r (r means any eight-bit register, and pq means any
address). Let's first look at the formats of these
statements as shown below.

Machine Code    Short For       Basic Equivalent
------------------------------------------------
IN A,(n)        IN A,(An)       LET A=IN(256*A+n)
IN r,(C)        IN r,(BC)       LET r=IN(256*B+C)
OUT (n),A       OUT (An),A      OUT 256*A+n,A
OUT (C),r       OUT (BC),r      OUT 256*B+C,r

Notice that the standard opcodes are incomplete. For
instance, there is an instruction called IN D,(C); however,
this is really just a shorthand notation - what it really
means is IN D,(BC). Also note that in the above table, A is
a register and n is a fixed constant. Now let's get down to
what these statements actually do.


What's it all about?

IN fetches a number from the outside world and loads this
number into a machine code register. OUT takes a number from
a machine code register and sends it to the outside world.
There are a number of ways in which the Z80 chip can
communicate with the outside world. One way is via the
keyboard. It can receive information from the keyboard using
IN instructions as shown in the table on the page [below.
JG.]

IN r,(FEFE) scans section 0:    V, C, X, Z and Caps Shift
IN r,(FDFE) scans section 1:    G, F, D, S and A
IN r,(FBFE) scans section 2:    T, R, E, W and Q
IN r,(F7FE) scans section 3:    5, 4, 3, 2 and 1
IN r,(EFFE) scans section 4:    6, 7, 8, 9 and 0
IN r,(DFFE) scans section 5:    Y, U, I, O and P
IN r,(BFFE) scans section 6:    H, J, K, L and Enter
IN r,(7FFE) scans section 7:    B, N, M, Symbol Shift and Space

The number you actually get from such an IN instruction is
best viewed in binary. Each such IN instruction tests five
specific keys on the keyboard, and for each key produces a 1
if the key is not pressed and a 0 if the key is pressed.
Bits 7, 6 and 5 will contain rubbish, bits 4, 3, 2, 1 and 0
test the keys in the order given above. For instance,
suppose keys Symbol Shift and B were held down
simultaneously, then IN r,(7FFE) would produce the (binary)
number xxx01101. The zeroes indicate that a key is pressed.
The xs are rubbish and their value is unimportant. You can
achieve this test in two ways: either LD BC,7FFE / IN r,(C)
or LD A,7F / IN A,(FE). See if you can deduce why both of
these are equivalent to "IN r,(7FFE)".

Another way of communicating with the outside world is via
the television screen - this is an output device (the
keyboard is an input device). The contents of the screen
itself are controlled by addresses in memory (4000 to 5AFF),
but the colour of the BORDER is actually changed by an OUT
instruction.Try typing (in BASIC) OUT 254,6 and see what
happens. Now press Enter on its own. What's happening is
that the ROM itself is re-setting the border colour (with
its own OUT instruction) to whatever number is given by the
system variable BORDCR. Try typing POKE 23624,6*8 and see
what happens. Now press Enter on its own.

The instruction, OUT (xxFE),r, in any form (where the xx can
be any byte whatsoever - its value is irrelevant), will
instantly change the border colour to the colour whose code
is contained in the register given. For example, LD A,02 /
LD C,FE / OUT (C),A will change the border colour to red.
Notice that we do not need to worry about the B register
because in this particular case its value is unimportant.
This is a side- effect of the Spectrum hardware, not of
machine code itself.


All change

To change tack completely I would like to introduce you to a
new set of instructions altogether - the ROTATE and SHIFT
instructions. In order to understand the effect of these
instructions it is useful to think of the numbers involved
in binary rather than Hex (useful, although not necessary).
Suppose A contains the number 00101101, and the carry flag
(which I shall refer to as K) contains the number 1. The
instruction RLA (which stands for Rotate Left A) will rotate
every bit one position to the left. The leftmost bit of A
moves into K, which itself moves into the rightmost position
of A. In the example given, the result would be that A
contained the number 01011011 and K would now be zero. On
the other hand, if RRA were used instead of RLA (guess what
RRA stands for!) then A would contain 10010110 and K would
contain 1. You can do the same for any register except that
the instructions are written differently - you write 'RL B'
(with a space) instead of 'RLB' (without a space). In fact,
there is actually an instruction called 'RL A' (with a
space) which at first glance appears to do exactly the same
as 'RLA' (without a space). The difference is that RL A will
change the value of the Z flag, whereas RLA will not.

Flags are funny things. Their concept is quite simple - a
flag is a one-bit register, or a register which can only
store the numbers zero and one. There are four flags we can
make use of: the carry flag, K, we've already seen; the zero
flag, Z - if an instruction changes it (some don't), it will
become 1 if the result of the instruction is zero, and 0 if
the result of the instruction is not zero; the sign flag, S,
becomes 0 for a positive answer, and 1 for a negative answer
(in other words it actually equals bit 7 of the answer); and
the parity / overflow flag, P, is changed in two ways: for
arithmetic instructions it becomes 1 if the number increases
from 7F or less to 80 or more, or if the number decreases
from 80 or more to 7F or less - for non- arithmetic
instructions it is set to 1 if the result contains an even
number of ones, or zero if it contains an odd number of
ones.

What is the point of the flag P? Its non-arithmetic use
(parity check) is absolutely useless as far as I can see.
Its arithmetic use (overflow check) comes into play when
you're doing sums. It assumes that numbers 80 to FF are all
negative (FF being minus one, FE being minus two, and so
on), and so since 42 (positive) plus 42 (positive) equals 84
(negative?) - we have an overflow!

Back to the ROTATE instructions. As well as RL and RR, there
is another breed of rotate instructions - RLC and RRC, which
stand for Rotate Left (or Right) without Carry. If A started
off as 00101101, then irrespective of the original value of
the carry, RLCA would change it to 01011010 and RRCA would
change it to 10010110. K would, in fact, be changed - its
new value would be the bit which came off one end and
emerged at the other, but its original value does not come
into play. As before, you should note that RLCA is subtly
different to RLC A, and that RLCB (etc) do not exist whereas
RLC B (etc) do.


Best of three

There are three different types of SHIFT instruction, and
all of them are written with a space. The first is called
SLA - the A stands for 'Arithmetic', so you have to put the
name of a register as well, eg SLA A or SLA B. SLA is a
little bit like RL except that instead of the carry moving
into the rightmost bit of the register, it just disappears
altogether. The rightmost bit of the register is instead
always reset to zero. In effect, the value of the register
has been multiplied by two. There are two similar
instructions to shift right which divide the appropriate
register by two. We have to remember though that numbers
between 80 and FF can each have two different meanings; for
instance, FE can mean either 254 or minus two. Now, 254
divided by two is 127 (in Hex 7F) whereas minus two divided
by two is minus one (in Hex FF) - so there must be a simple
way to overcome this problem. There are two different Shift
Right instructions, called SRA and SRL. SRA (Shift Right
Arithmetic) treats numbers between 80 and FF as negative and
divides them by two. Its precise effect is to move each bit
one position to the right, with the rightmost bit moving
into the carry, and the leftmost bit remaining unchanged
(bit 6 and bit 7 are always identical immediately after a
SRA instruction). SRL (Shift Right Logical) treats numbers
between 80 and FF as positive and divides them by two. Its
precise effect is to move each bit one position to the
right, with the rightmost bit moving into the carry, and the
leftmost bit becoming zero.

There is one more instruction I need to give you before the
program will make sense - the instruction HALT. HALT in
machine code is quite similar to PAUSE 1 in BASIC. Its
precise effect is to wait (for a maximum of 1/50th of a
second) until the next TV frame has been output to the
screen, and then to continue from the next instruction. Its
use, as I have made use of it, is in synchronisation -
avoiding that horrid jump you sometimes get when you change
border colours halfway through the screen outputting.

The program I promised you is called Tricolours and is
listed separately. You now have sufficient knowledge to be
able to understand the listing. I suggest that you read the
listing and see if you can work out exactly what it does,
and then type it in to see if you were right. I think you
will find it reasonably impressive, and, in fact, a rather
surprising display coming as it does from a ZX Spectrum.


Programming techniques

There are several ways to learn machine code. One way is for
me to give you a program and explain why it works. Another
way is for me to write a program and not explain how it
works so that you have to work it out for yourselves -
unfortunately, deciphering other people's programs
(especially mine) turns out to be quite tricky. Several
people have commented that they've learned far more machine
code from ironing out the bugs in my programs than they have
from the ones which worked, and actually wondered if the
bugs were put there deliberately! (I confess they weren't -
this brilliant teaching method occurs purely as a
consequence of typing errors and the like!).

However, there is one last technique by which to learn
machine code, and that is to write it yourself. You take a
scrap of paper and scribble down a few ideas, turn the ideas
into some sort of coherent pattern of how you think it ought
to work (either with or without a flow diagram - it doesn't
matter), elaborate it into a program or part of a program,
turn it into Hex, feed it in, watch it crash, turn around
three times, jump up and down, and sing the National Anthem
wondering why it went wrong!


Crash landing

Programs go wrong all the time as you're writing them,
especially in machine code. The difference is that in
machine code a crash is usually fairly fatal, sometimes even
meaning that you have to actually switch the machine off
before it will behave sensibly. (The Spectrum usually
behaves quite sensibly when it's switched off.) All I'm
really trying to say is that when this happens (note: when,
not if) that you shouldn't be too disheartened and that you
should just go back and try again, and try to find out what
the error is. Usually there is an error - it is quite
unusual for the breakdown to be caused by a glitch in the
Spectrum itself, especially if you happen to find a
particular glitch which only ever occurs when you try to
play with machine code.

The best possible advice I can give as regards writing
programs is to make all of your programs as short as you
possibly can. This will train you to write programs
efficiently. Making programs short involves putting all of
the machine code instructions to their best possible use,
and sometimes using clever little tricks to save space; for
instance, using SBC HL,HL, instead of LD HL,0000 when you
know that the carry is zero. One really useful instruction
which really does save space is the instruction LDIR (LD,
Increment and Repeat) which is equivalent to a whole set of
instructions: "LD(DE),(HL) / INC HL / INC DE / DEC BC /
repeat until BC=0"; and can be used to copy several bytes at
once from one address to another. Study the program Newcols
which uses this instruction - it instantly changes the
foreground and background colours on the screen to those
colours contained in registers B and C.

Notice that in order to use Newcols as it stands now, you
have to do two POKE instructions before RUNning it in order
to define what the foreground and background colours will
be. Although this is not difficult, I personally consider it
a nuisance, and a far, far better programming technique is
as follows.

Insert the BASIC line DEF FN U(X,Y) = USR address of label
PARAMS at any point in the program. Now you have complete
control - you may change the PAPER and INK colours at will
using statements like LET L=FN U(2,6) or LET L=FN
U(7,INT(4*RND)). The reason this works is because a DEF FN
statement line is actually POKEd for us by the ROM. When a
reference to FN is made, the values for it are calculated by
the ROM, POKEd into the DEF FN statement, and then
calculated. Once POKEd, the BASIC DEF FN statement looks
like this (those bytes underlined are invisible from the
BASIC listing):

DEF FN U(X 0E xx xx xx xx xx, Y 0E yy yy yy yy yy)
           -- -- -- -- -- --    -- -- -- -- -- --
= function expression.

The bytes I have written as xx xx xx xx xx and yy yy yy yy
yy are the five byte Sinclair form of the two numbers in the
FN U expression. If the two numbers are both integers
between 00 and FF (which in this case they will be) then the
Sinclair forms will actually be 00 00 xx 00 00 and 00 00 yy
00 00. The system variable DEFADD points to the first
variable name inside the brackets (in this case to the x) so
that LD HL,(DEFADD) / INC HL / INC HL will point HL to the
first byte of the Sinclair form of the first number.

You can use this technique not just for this program, but
for any machine code program which requires the passing of
parameters from BASIC.

With that I shall leave you for now. In my next article,
I'll be putting some concentration into efficient use of the
stack, among other things.


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

Tricolours

Use HEXLD3 to load this into the ZX Spectrum. The BASIC part of the
listing is as follows:

 700 BORDER 1: CLS
 710 FOR i=1 TO 6: PRINT PAPER 2,TAB 0: NEXT i
 720 FOR i=1 TO 12: PRINT PAPER 7,TAB 0: NEXT i
 730 FOR i=1 TO 4: PRINT PAPER 1,TAB 0: NEXT i
 740 RANDOMIZE USR 32768
 750 STOP

And here is the machine code part of the listing (to address 8000):

01FE7F   START   LD BC,7FFE
76       LOOP    HALT              Wait for next TV frame
3E02             LD A,red
219D03           LD HL,039D
CD1E80           CALL STRIPE
3E07             LD A,white
213A03           LD HL,033A
CD1E80           CALL STRIPE
3E01             LD A,blue
ED79             OUT (C),A
ED78             IN A,(C)          Remember, it's really IN A,(BC)
1F               RRA               K = test on Space key
38E6             JR C,LOOP         Loop if Space key not pressed
C9               RET               Return to BASIC
ED79     STRIPE  OUT (C),A         This is a subroutine
2B       DELAY   DEC HL            HL is the timing for a delay loop
7C               LD A,H
B5               OR L              Is HL=0?
20FB             JR NZ,DELAY       Keep looping until HL=0
C9               RET               End of subroutine

Use RUN 700 to RUN this program; press the Space key to exit from it.


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

Newcols

78       NEWCOLS LD A,B            A = PAPER colour
76               HALT              Synchronise with TV frame
D3FE             OUT (FE),A        Change BORDER colour to new PAPER colour
78               LD A,B            A = PAPER colour
87               ADD A,A
87               ADD A,A
87               ADD A,A           Multiply A by eight
81               ADD A,C           Add INK colour
210058           LD HL,5800        Point HL to 1st attribute byte
110158           LD DE,5801        Point DE to 2nd attribute byte
01FF02           LD BC,02FF        BC = number of attribute bytes,
                                     excluding the 1st one
77               LD (HL),A         Set 1st attribute byte
EDB0             LDIR              Set all remaining attribute bytes
C9               RET               End of subroutine

To run from BASIC, add these machine code instructions, POKE into the
first statement, and CALL from the label START using USR:

01????   START   LD BC,????        Set PAPER and INK colours
18E5             JR NEWCOLS        Execute machine code above

For use with FN U:

2A0B5C   PARAMS  LD HL,(DEFADD)
110400           LD DE,0004
19               ADD HL,DE         Point HL to PAPER colour
4E               LD C,(HL)         C = PAPER colour
19               ADD HL,DE
19               ADD HL,DE         Point HL to INK colour
46               LD B,(HL)         B = INK colour
18DB             JR NEWCOLS        Execute machine code above


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