SINCLAIR PROGRAMS March 1985

SMOOTH SCREEN SCROLLING IN MACHINE CODE

In part two of his series dealing with m/c Tony Rickwood looks at simple commands


IN THE last issue, I introduced some of the background concepts of machine
code programming. We are now ready to make a start on practical machine
code by looking at two short routines for scrolling the display to both
sides of the screen.

Take a quick look at the two routines listed in this article and you will
see how just a few machine code instructions can achieve a smooth scroll,
pixel by pixel. First, a few notes about the format which will be used to
present all examples in the series. Each program is presented in two parts:
a BASIC program followed by an Assembler listing. To use a routine, it is
not essential for you to understand the assembler or to use an assembler to
enter it. All machine code is contained in and entered into memory by the
BASIC program which will also demonstrate the routine in execution. It will
even save the machine code for you to build up a dedicated toolkit of
routines for you to call from your own BASIC programs.

The assembler listing is there to help you understand what is being done by
the numbers POKEd into memory by the BASIC program. Much of what I will
have to say in this series will teach you machine code by explaining the
assembler instructions. The important thing, though, is to get the routine
running first from the BASIC program and then to settle down to understand
how it works.

SCROLL RIGHT: BASIC
First, enter and run the BASIC listing for Program one. Provided you enter
the data correctly, you will see a screen listing of the program
disappearing to the right.

Line 10 does most of the work by reading the machine code as decimal
numbers and POKEing them into a part of spare memory. 24 bytes of spare
memory from locations 64000 to 64023 are used in this case. What do we mean
by spare memory? Page 165 of your Spectrum manual gives you a complete
memory map. The important things to find out are:

a) How high can a spare byte be? When you first switch on, RAMTOP (the
top of available RAM) is set to the byte immediately preceding the part of
memory reserved for UDGs. This can be found by: PRINT PEEK 23730 +
256*PEEK 23731 which evaluates the system variable called RAMTOP (see page
176 of the manual). On power up, this will be 65367 for a 48K machine and
32599 for a 16K machine.

b) How low can a spare byte be? This will depend mainly on the size of any
BASIC program and variables, and can be found by: PRINT PEEK 23653 +
256*PEEK 23654 which evaluates the system variable called STKEND.

Subtracting high and low bytes gives you the number of spare bytes so you
can change the numbers in line 20 to suit your own requirements (and
machine ie. 16 or 48K).

Line 30 checks your data entry in the DATA statement (line 40), as an
attempt to execute defective machine code will often cause the machine to
crash. Line 60 executes the routine following a CLEAR command. This lowers
RAMTOP to ensure that the routine is not erased when NEW is pressed, or
another BASIC program is loaded (remember that you will often want to use
the same machine code routine with different BASIC programs). The LIST is
necessary to give you a display to be scrolled when the routine is executed
by RANDOMIZE USR 64000.

SCROLLING RIGHT: ASSEMBLER
This has been prepared using one of the better commercially available
assembler programs. To reiterate, you do not have to enter this unless you
have an assembler of your own and wish to experiment with different ways of
doing the same job (a good way to make learning machine code faster and
more enjoyable). If you do not like the idea of precooked machine code, and
do not own an assembler, you may wish to use a Hex Loader. There are many
such utility programs listed in books and magazines which you can key in in
BASIC. These will accept the hexadecmial equivalents to the Z80 mnemonics
as they appear in assembler listings, convert them to decimal numbers, and
POKE them into memory. If you wish to use a hex loader then, instead of my
BASIC interface programs, the hex equivalents are given in my assembler
listings (first column).

The second column shows assembler line numbers which I will refer to
frequently for explanatory notes. These are followed by the assembler
instruction. Anything preceded by a semi-colon is like a BASIC REMark. To
see the relationship between Assembler, hexadecimal equivalents and the
decimal numbers you have entered in the BASIC program, have a close look at
line 50 : LD HL,16383. The hex equivalent of a Z80 instruction of this
form is "21 xx xx" where "xx xx" is the two byte representation of the
number to be loaded into register pair HL (which is what the CPU
understands by the hex code "21"). In machine code terminology, a number in
the range 0 to 65535 has to be split into a "high order" and a "low order"
byte (HOB and LOB). HOB is the number of times a number will divide by 256
and LOB is the remainder. Thus: HOB = INT (n/256) and LOB = n256*INT(n/256).
The HOB and LOB for 16382 are 63 and 255 respectively (in hex, 3F and FF).

You may recall from a previous article on Numbers that the LOB is stored
first, so this convention is also used with machine code instructions.
Therefore, the instruction we are examining here becomes "21 FF 3F" in hex.
In decimal, this is "33 255 63," as you will find it in the BASIC listing.
When running, the CPU will automatically interpret the binary
representation of "33" (BIN 00100001) as instructing it to load register H
with the binary form for "63" and register L with that for "255".

Now we can start to understand how this little routine hangs together, but
do make sure you properly understand the previous paragraph as it is
fundamental to all machine code programming.

Firstly, we must define what the routine has to do. This can be simply
stated as "moves the whole screen to the right, pixel by pixel". We can
visualise a display as a matrix of dots (black or white) which spread 256
wide (32 columns *8) and 192 deep (24 rows *8). Next, we must decide how
this is to be done. The method I have used is to work through each
consecutive byte of the display file, working with each of the 32 bytes
(representing 32 columns) across the screen before moving down to the next
pixel row. Remember that there are 192 pixel rows and it is these rows (not
the 24 character rows) with which we will be dealing.

The display file which is used to store all 6144 bytes (32*192) of data
which make up the display starts at location 16384 and ends at 22527. Our
scroll routine has to change the bit pattern of all these bytes, not just
once (as this will only give a single pixel move for the whole screen) but
256 times.

For the BASIC programmer, this immediately brings to mind the concept of
nested FOR/NEXT loops. There are three such loops in this routine, labelled
PIXEL, ROW and COL (for COLumn). The outer PIXEL loop serves to repeat for
256 pixel moves (so that the leftmost pixels are the last to disappear).
The middle ROW loop works down through 192 rows and the inner COL loop
works through the 32 bytes in each row. We can break down our analysis by
looking at what goes on in the inner loop first and working outwards.

The COLumn Loop:
The lines of interest here are numbered 80 to 120. Two are to initialise
this loop, starting with XOR A. This stands for "eXclusive OR on register
A" and represents one of three commonly found logical operators (OR, AND,
XOR). We shall be seeing much more of all three in future examples. For the
moment all you need to know is that this instruction has the important
property of clearing the carry flag. You will see how the carry flag is
used to hold the bit which is forced out of each byte to become the first
bit of the next byte. Remember that we are at the start of a pixel row at
this point in the routine. To ensure that the last bit of the previous row
does not become the first bit of the current row, it is therefore vital to
reset the carry flag to zero.

"LD B,32" means "Load Register B with the number 32". I explained the
principle of addressing in the previous article. You need to know that
there are several types of addressing. The type being used here is called
immediate addressing. In plain language, it means that register B can be
loaded immediately with a number instead of having to look up a value
somewhere in memory. In BASIC, an analogy would be let B=32 instead of
LET A=32: LET B=A. The command sets up a loop counter similar to that of a
FOR B=32 to 1 STEP -1 and covers the 32 bytes (for each column) in a row.

Line 100 is the first instruction to be executed in the inner loop proper.
The part which is converted to machine code by the assembler is "INC HL",
as "COL" is just a label to mark the start of the loop. "INC HL" is read as
"INCrement the contents of the HL register pair by 1". As we will see, HL
is used to point to each consecutive byte of the display file.

The real work in this routine is done by line 110. "RR (HL)" which is read
as "Rotate Right the contents of the address pointed to by HL (ie. the
current byte of the display file)". This is an example of an instruction
which uses indirect addressing. Here, the CPU is working indirectly on a
memory location to which the HL register pair is pointing. The appearance
of brackets in machine code can generally be read in this way. Why do we
not use direct addressing by calling in the number to be operated on, doing
the operation, and putting it back again? We could, but this would require
three instructions. So indirect addressing makes machine code more compact
and efficient.

Figure one shows how "RR" works on a byte of data. Used once, it moves bit
7 to bit 6, bit 6 to bit 5 etc, down to bit 0 which is moved to occupy the
carry flag. What was the carry flag is moved to bit 7. Thus, as we move
across the 32 bytes of a pixel row, each byte is moved one pixel to the
right, with the carry flag holding the overflow bit ready to start the next byte.

The COLumn loop is terminated by Line 120, "DJNZ COL" which reads as
"Decrement the B Register and Jump to label COL if the contents of B are
Non-Zero". DJNZ must always be used in conjunction with B holding the loop
counter and gives us the nearest machine code equivalent of the FOR/NEXT
loop. The assembler program automatically works out which instruction has
been assigned the label COL and enters an offset (number of steps backward)
into the machine code for DJNZ.

The ROW loop:
This is initialised by line 50, which points HL to the byte immediately
below the start of the display file so that, the first time INC HL is
executed, HL is pointing to the start of the display. Line 60 sets up the
loop counter for 192 rows.

Now we hit a small snag. As we have seen, all DJNZ loops must use the B
register as the loop counter so, for nested loops, we must be able to store
an outer loop counter while an inner loop is being processed. We could load
B into another register and load it back again, although there are two
instructions which are more compact. These are "PUSH BC" and "POP BC".

Look at your Spectrum manual again, and you will see a chunk of memory
called the stack. This is a place where numbers can be stacked on top of
one another. It is a convenient place for the machine code programmer to
store numbers temporarily, without having to worry about addressing.

To use the stack, a number PUSHed onto it from a register pair, and POPped
off again when required. For example, suppose the CPU is about to start on
the 50th row. Line 70 will PUSH the number 50 (along with whatever register
C is holding  this is irrelevant in this example) onto the stack. It can then
work through the COL loop with B = 1 TO 32. The B=50 is then POPped back off
the stack, and the DJNZ in line 140 decrements it to 49, ready for the next
row.

The PIXEL loop:
Starts at line 40 and ends on line 160. The same principles apply as for
the inner loops, except that here we are executing the single pixel move
(for whole screen), 256 times. You need not worry that all the PUSHing and
POPping will confuse matters because the CPU automatically works with the
stack on a first in, last out basis.

You might be a bit puzzled by line 30, "LD B,0" for 256 repetitions.
Imagine a register as a milometer which can only register up to 255 miles
before re-setting to zero. If we set it to zero to start with, then the
first time the DJNZ PIXEL instruction is executed, our milometer will be
turned back to 255. Therefore, another 255 passes through the outer loop
will be required to bring B down to zero and finally allow the CPU to exit
back to BASIC through the RETurn instruction.

Program 2 lists the routine for a left pixel scroll. As you would expect,
the assembler listing is very similar to Program One except that now we are
working backward through the display file. Therefore, we need to DECrement
HL (from the end of the display file) and "Rotate Left" (RL) each byte of
the display.
