Hardware World Issue 41 Contents Hit Squad

Machine Code



Shaping up with stacks of data

How to store data in Z80 code; Marcus Jeffery fills in on assemblers and colours in shapes

One of the biggest problems when using Z80 machine code is finding places to put numbers. In Basic, there are plenty of variables and arrays which can be used to store data, but in Z80 there are just a few registers.

One way around that is to use an instruction:

  LD (address), register

which would place the value of the register into the contents of the specified address. A better method, however, is known as the 'stack'.

A stack is a type of data structure, in much the same way as an array, list, queue and so on. With an array, you tell the computer which element you want by giving the array subscripts. With a list, you start at the 'head' and move towards the 'tail'. A queue is known as a First In First Out (FIFO) structure because you add items to the end of the queue and take them away from the start.

A stack is a Last In First Out (LIFO) structure. Imagine a stack of plates. Trying to take a plate from the middle of the stack is likely to have disastrous results. You will probably also have problems if you try to put a plate into the middle. You can only safely place and remove plates from the top. In Z80 machine code, those operations are known as PUSH and POP.

Z80 only allows you to PUSH and POP two registers at a time, giving the instructions:

  PUSH AF ; POP AF
  PUSH BC ; POP BC
  PUSH DE ; POP DE
  PUSH HL ; POP HL

So, for example, the code

  LD HL,1234
  PUSH HL
  LD HL, 4321

would leave HL containing the number 4321. However, if we now execute the instruction

  POP HL

then the number 1234 will be restored to HL. One interesting point is that the stack does not know which registers it has stacked; all it has done is place two bytes onto the stack. Therefore, instead of the last instruction, we could just as easily use, say,

  POP BC

which would overwrite the contents of the BC registers with the number 1234.

The main point to remember when using PUSHes and POPs is that they must always be a matching pair. If you allow values to be stacked without POPping, the area set aside for the stack will eventually run out of room. The location of the top of the stack is held in a register pair called, surprisingly, the Stack Pointer, which is abbreviated to SP. Bearing that in mind, another useful instruction is

  EX (SP), HL

which will exchange the last two bytes placed on the stack with the contents of the HL register pair.

Remember that the brackets indicate that we want the 'contents of the location addressed by' the contents of the Stack Pointer, not the contents of the Stack Pointer. Those instructions are shown at the end of the article in figure four. That also shows a few other instructions which operate on the Stack Pointer, though you're unlikely to find those very useful as yet.

Though your use of the stack is likely to be limited to the above instructions under most circumstances, it is far more flexible. For example, you will have noticed the instructions CALL and RET in recent programs. Those act in the same way as GO SUB and RETURN in Spectrum Basic. The instruction

  CALL address

transfers execution to the specified address, and

  RET

returns to the instruction immediately following the CALL. However, when that happens, the computer has got to know where to return to. Consequently, when the CALL instruction is executed, the computer PUSHes the value of the location following the CALL statement onto the stack. Then, when a RETurn statement is found, the stack is POPped to tell the computer which code to execute next.

Now we are ready for this month's example program, which will allow you to fill in any shape on the screen. To run this just type in and run the Basic program in figure one - the Hex Loader is the same as usual. To use the routine generally, POKE the x coordinate into location 23728 and the y coordinate into 23729. Those two locations among the system variables - User Guide, page 130 - are normally unused.

Figure two shows the equivalent in assembly code. The PBYTE routine takes the screen coordinates 'x' and 'y' - in C and B, respectively - and calculates the coordinates of the screen byte, placing the result into the HL register pair. In addition, a single bit is set in the A register, corresponding to the bit within the HL byte which represents the (x,y) coordinate. The letters in brackets, given in the comments on the right-hand side of the listing, refer to figure three, which should help to explain how the BC coordinates are converted into the HL location.

The routine works by filling in the (x,y) pixel - assuming that it is not already filled - then doing the same for the pixels to the North, South, East and West. Those in turn, if filled, will fill in four directions, and so on.

In that way, the whole shape is filled. By writing a general routine - FBYTE - to carry out the operation, it can 'call itself' to fill to the North, South, East and West!

Remember that the return locations from the CALLs are all stacked, so the routine always knows where to return to. The only problem is that the (x,y) coordinates in BC would be overwritten, so we must stack those too. The comments marked with asterisks show which instructions are connected with saving and restoring values - imagine how messy that would be without the stack!

That is by no means the quickest way of filling shapes, but is probably quick enough for most purposes. You might like to try it out in Basic. You can easily implement a stack, as follows:

         1 DIM s(1000)
         2 LET sp = 0
PUSH: 1000 LET s(sp) = xcoord
      1010 LET s(sp+1) = ycoord
      1020 LET sp = sp+2
      1030 RETURN
POP:  2000 LET sp = sp-2
      2010 LET ycoord = s(sp+1)
      2020 LET xcoord = s(sp)
      2030 RETURN

The major problem with this method of filling shapes is that it uses enormous amounts of stack space. If you try to fill large shapes on the screen, you may find the whole computer crashing as the stack runs out of space. The easy way to avoid that is to split large spaces into a number of smaller shapes.

Assemblers

If you are sufficiently attracted to the advantages of using machine code, then you will probably consider purchasing an Assembler package. The assembly code listings given in these articles use Spectre-Mac-Mon from Oasis Software, but other assemblers may have slightly different formats.

Assembly code is usually divided into a number of fields. Those are

  LABEL MNEMONIC
  OPERAND COMMENT

In some cases, such as the Zeus Assembler from Sinclair Research, the source listing also contains line numbers, as in Basic. The first thing to look for is the method of entering those formatted instructions. On- screen editors are obviously very useful. One very important feature is the use of labels - don't buy it if it doesn't use them.

In addition to the normal Z80 mnemonic instructions, most assemblers include 'pseudo-operations'. Those include such mnemonics as ORG, to tell the computer where in memory to assemble the code. EQU allows constants to be assigned label names, making the listing more readable. DB - or similar - standing for Define Byte, allows memory locations to be initialised to particular values.

It is often possible to define 'macros', which allow you to assign a name to a sequence of instructions. Conditional assembly allows you to decide whether certain pieces of code are assembled at assembly-time. Many more features may be found, varying between assemblers.

Many assemblers include 'monitors'. Those allow you to examine memory, while running machine code. Though not absolutely necessary, the more serious machine code user will find those invaluable. Some important additional features to look for are the setting of 'break points', single-stepping, examination of registers, intelligent copy, modify memory and disassembly.

When converting listed programs for your own assembler, the two main points to check are:

The Format: assemblers differ in the fields, length of labels, format of numbers, and so on;

The Memory: assemblers are likely to use different areas of memory. For instance, the routines given have all started from location 60000. However, the Zeus Assembler uses that as workspace, so the routines should be changed to start at, say, 30000.

In all cases, check with the documentation given with your assembler.

Figures

Figure 1

  10 CLEAR 59999
  20 GO SUB 1000
  30 CLS
  40 CIRCLE 120,100,20
  50 POKE 23728,120: POKE 23729,100
  60 RANDOMIZE USR 60000
 100 STOP
1000 REM HEX LOAD ROUTINE
1010 DEF FN p(x)=CODE h$(x)-48-7*(CODE h$(x)>=65)
1020 LET byte=0
1030 RESTORE 2000
1040 READ start
1050 READ h$
1060 IF h$="*" THEN GO TO 1160
1070 IF LEN h$<>2*INT (LEN h$/2) THEN PRINT "Odd number of hex digits in: ";h$: STOP
1080 FOR i=1 TO LEN h$
1090 IF NOT((h$(i)>="0" AND h$(i)<="9") OR (h$(i)>="A" AND h$(i)<="F")) THEN PRINT "Illegal hex digit: ";h$(i): STOP
1100 NEXT i
1110 FOR i=1 TO LEN h$ STEP 2
1120 POKE start+byte,16*FN p(i)+FN p(i+1)
1130 LET byte=byte+1
1140 NEXT i
1150 GO TO 1050
1160 PRINT "Code entered"
1170 PAUSE 150
1180 RETURN
2000 DATA 60000,"ED4BB05C"
2010 DATA "CD68EA","C9"
2020 DATA "C5","CD8EEA","C1"
2030 DATA "5F","A6","C0","7B"
2040 DATA "CDBEEA","C5","04"
2050 DATA "3EB0","90","C468EA"
2060 DATA "C1","C5","05"
2070 DATA "E468EA","C1","C5"
2080 DATA "0C","C468EA","C1"
2090 DATA "0D","E468EA","C9"
2100 DATA "79","E607","5F"
2110 DATA "CB39","CB39","CB39"
2120 DATA "3EAF","90","47"
2130 DATA "E638","CB27","CB27"
2140 DATA "B1","6F","78","E607"
2150 DATA "67","78","E6C0"
2160 DATA "CB3F","CB3F","CB3F"
2170 DATA "84","C640","67","43"
2180 DATA "04","AF","37","CB1F"
2190 DATA "10FC","C9"
2200 DATA "B6","77","C9","*"

Figure 2

                     ORG   60000
                     LOAD  60000

EA60 ED4BB05C FILL   LD    BC,(23728) ;Get start coordinates
EA64 CD68EA          CALL  FBYTE      ;Fill from here
EA67 C9              RET              ;Return to BASIC

EA68 C5       FBYTE  PUSH  BC         ;Stack BC *
EA69 CD8EEA          CALL  PBYTE      ;HL = Screen address
EA6C C1              POP   BC         ;Restore BC (coordinates) *
EA6D 5F              LD    E,A        ;Save A (bit mask) *
EA6E A6              AND   (HL)       ;If Screen bit not empty,
EA6F C0              RET   NZ         ;    then RETurn
EA70 7B              LD    A,E        ;Restore A *
EA71 CDBEEA          CALL  PSCRN      ;Fill the screen point
EA74 C5              PUSH  BC         ;Stack coordinate *
EA75 04              INC   B          ;Look NORTH
EA76 3EB0            LD    A,176
EA78 90              SUB   B
EA79 C468EA          CALL  NZ,FBYTE
EA7C C1              POP   BC         ;Restore coordinate *
EA7D C5              PUSH  BC         ;Stack coordinate *
EA7E 05              DEC   B          ;Look SOUTH
EA7F E468EA          CALL  PO,FBYTE
EA82 C1              POP   BC         ;Restore coordinate *
EA83 C5              PUSH  BC         ;Restore coordinate *
EA84 0C              INC   C          ;Look EAST
EA85 C468EA          CALL  NZ,FBYTE
EA88 C1              POP   BC         ;Restore coordinate *
EA89 0D              DEC   C          ;Look WEST
EA8A E468EA          CALL  PO,FBYTE
EA8D C9              RET

EA8E 79       PBYTE  LD    A,C
EA8F E607            AND   7          ;Get bit in byte (b)
EA91 5F              LD    E,A
EA92 CB39            SRL   C          ;Get (a) in register C
EA94 CB39            SRL   C
EA96 CB39            SRL   C
EA98 3EAF            LD    A,175      ;Convert y-coord, to give
EA9A 90              SUB   B          ;    (0,0) at top-left
EA9B 47              LD    B,A
EA9C E638            AND   56         ;Get (d) in register A
EA9E CB27            SLA   A
EAA0 CB27            SLA   A
EAA2 B1              OR    C          ;Get (d)+(a) in register A
EAA3 6F              LD    L,A        ;    then move to register L
EAA4 78              LD    A,B
EAA5 E607            AND   7          ;Get (e) in register A
EAA7 67              LD    H,A        ;Place temporarily in H
EAA8 78              LD    A,B
EAA9 E6C0            AND   192        ;Get (c) in register A
EAAB CB3F            SRL    A         ;Move to position
EAAD CB3F            SRL    A
EAAF CB3F            SRL    A
EAB1 84              ADD    H         ;Add (e)
EAB2 C640            ADD    64        ;Add screen base address
EAB4 67              LD     H,A       ;Place in register H
EAB5 43              LD     B,E       ;B = bit in byte (0-7)
EAB6 04              INC    B         ;B = bit in byte (1-8)
EAB7 AF              XOR    A         ;Clears A register
EAB8 37              SCF              ;Set carry flag, then
EAB9 CB1F     PBLOOP RR     A         ;    rotate carry into
EABB 10FC            DJNZ   PBLOOP    ;    bit position in A
EABD C9              RET

EABE B6       PSCRN  OR     (HL)      ;Set screen bit by ORing
EABF 77              LD     (HL),A    ;    mask with current screen
EAC0 C9              RET

                     END

Workarea - A800 to A956
ORG  end - EAC1
LOAD end - EAC1

Figure 3.

Figure 3.

Figure 4. New Z80 instruction codes

PUSH      rp        - place the register pair (AF,BC,DE,HL) onto the top of
                      the stack
POP       rp        - remove the top two bytes from the top of the stack, 
                      placing the contents into the register pair (AF,BC,DE,HL)
EX        (SP),HL   - exchange the two bytes from the top of the stack
                      with the contents of the HL register pair

Of the instructions we have seen so far, the following can also be used
with the Stack.

ADD       HL,SP     - add the Stack Pointer to the HL register pair
DEC       SP        - decrement the Stack Pointer by one
INC       SP        - increment the Stack Pointer by one
LD        SP,(addr) - set the Stack Pointer to the contents of the specified
                      address
LD        SP,data   - place the given data into the Stack Pointer
LD        SP,HL     - set the Stack Pointer to the contents of the 
                      HL register pair
LD        (addr),SP - place the contents of the Stack pointer into the given
                      address

Previous article in series (issue 40)
Next article in series (issue 42)



Hardware World Issue 41 Contents Hit Squad

Sinclair User
August 1985
Another Fine Product transcribed by Jim Grimwood at YRUA?