                    The Value of Tables



        Pete Cooke, author of Tau Ceti and Academy,

      lets us in on some of his machine code secrets.



One of the most effective techniques in machine code is the

lookup table. In Basic lookup tables correspond roughly

with single-dimension arrays holding tables of data, for

example:

 10 DIM a(20)

 20 FOR n=1 TO 20

 30 READ a(n)

 40 NEXT n

 50 DATA <whatever you want>

   In machine code lookups are much more common as they

give a way of evaluating complex functions without writing

masses of complex code. Two good examples of the use of

lookup tables that spring to mind are SIN tables and SCREEN

ADDRESSES.



 Screen Addresses



One of the biggest hurdles to graphics programming on the

Spectrum is the awful layout of the display file. Anyone

watching a SCREEN$ loading will have seen that the display

lines are stored in a non-sequential fashion and a great

deal of time and effort is needed to write a fast and effi-

cient routine to work out the screen address of any line.

   A much simpler way is to generate a lookup of screen

addresses in memory. I normally use a short BASIC program

to do this. (Listing 1).

   As an additional check that the program is working the

program is working the line:

 95 POKE address,255

fills the first byte of each line of the screen as the

program calculates its address.

   When the program has finished type GO TO 130 to save the

table. [The Basic program is on the TZX as "maze table"; it

doesn't auto-run because (as the REMs point out) it doesn't

include a CLEAR statement, which has to be supplied by

hand. The resulting code, saved at 65024, or hex #FE00,

follows it as "ytable". Note that this table does spill

over into the UDGs, and so also scribbles over the normal

Basic stacks; a CLEAR is therefore not merely wise with

this one, but strictly necessary.]

   I normally store a table like this as high in memory as

possible but if your assembler is stored at a high address

then change the value lookup to somewhere safe below it

(it's best to use a multiple of 256 for reasons explained

later).

   Having got this table into memory screen addressing now

becomes much easier. To get a screen byte address we only

need a short segment of code like the one in (Listing 2).

   In fact we can do even better than this by ensuring the

table starts at a multiple of 256 bytes. In that case the

low byte of the table's address is always 0 (Listing 3).

[I've added all assembly listings at the end of this file,

because the comments are quite clarifying. Both versions of

GETADD are on the TZX as well. Both routines are themselves

relocatable, but they do both expect ytable to have been

loaded at #FE00, or 65024.]

   Although this looks as long as the first version there

are several improvements. First the DE register pair is

not used. Second most of the instructions are single byte

instructions which operate much faster and take up less

space on a Z80.

   Finally here are 2 simple examples using the Ytable

lookup. The first is a fancy clear screen (this won't touch

the attributes) (Listing 4) and the second is a routine to

pixel scroll a window upwards (Listing 5).

   [These, too, are on the TZX, and these, too, both expect

ytable to be present at #FE00 already. Listing 4 is called

"FancyCLS". It is not relocatable, because it contains an

absolute CALL to its internal GETADD. It can, however, be

usefully called from Basic.

   Listing 5 is "PixScroll". Its test code (see the listing

below) can be called from Basic, but is not relocatable;

the routine itself is, but can't be called from Basic. As

the listing points out, you'd probably only want to use the

final part of it in your finished program anyway.]

   The beauty of lookup tables for screen addresses is

that, with the table in memory, graphics routines become

much more straightforward and dozens of routines in a long

program can use the same table.

   [At the end of the TZX, after "PixScroll", you'll find

all the assembly listings, saved from HiSoft Devpac 4.1. I

don't know whether these can be loaded in any other version

of Devpac, let alone other assemblers, but it's worth a try

in any case.

                              Richard Bos, November 2012. ]

___________________________________________________________

                         Listing 2

 10        ORG #C000

 20        ENT #C000

 30 YTABLE EQU #FE00 ;Whatever lookup is

 37 ;

 40 ;

 50 ;Get Byte Address

 60 ;Entry B=Line

 70 ;      C=Column

 80 ;Exit  HL=Address

 90 ;

100 GETADD LD   L,B

110        LD   H,0

120        ADD  HL,HL ;2 bytes in each table value

130        LD   DE,YTABLE ;Base of the table

140        ADD  HL,DE ;From offset into table

150        LD   A,(HL) ;Pick up low byte

160        ADD  A,C ;Add on column offset

170        LD   E,A ;Into low byte of DE

180        INC  HL ;Point HL to high byte

190        LD   D,(HL) ;Pick it up

200        EX   DE,HL ;Transfer to HL

210        RET  ;Done

220 ;

___________________________________________________________

                         Listing 3

 10        ORG #C000

 20        ENT #C000

 30 YTABLE EQU #FE00 ;Whatever lookup is

 40 ;

 50 ;Get Byte Address (Improved version=YTABLE on page boundary)

 60 ;Entry B=Line

 70 ;      C=Column

 80 ;Exit  HL=Address

 90 ;

100 GETADD LD   L,B ;B=Screen Line

110        SLA  L ;2 bytes per value (+carry)

120        LD   A,YTABLE/#100

130 ;This is the high byte of the start address of the table

140        ADC  A,0 ;Add any carry from the shift

150        LD   H,A ;Form table offset in HL

160        LD   A,(HL) ;Pick up low byte

170        ADD  A,C ;Add on column offset

180        INC  L ;Now only L needs inc

190        LD   H,(HL) ;Pick it up

200        LD   L,A ;Put low into L so HL=address

210        RET  ;Done

220 ;

___________________________________________________________

                         Listing 4

 10        ORG #C000

 20        ENT #C000

 30 YTABLE EQU #FE00 ;Whatever lookup is

 40 ;

 50 ;Example 1.

 60 ;

 70 ;Using GETADD for a fancy Clear Screen

 80 ;(Attributes not touched)

 90 ;

100 FANCY  LD   B,192 ;No of screen lines

110        LD   C,0

120 ;

130 F_LOOP PUSH BC ;Save the counter

131        DEC  B ;As DJNZ only loops to B=1

140        CALL GETADD ;HL = address of line

170        LD   BC,31 ;One less than no of bytes/line

180;32 bytes on each line

190        LD   E,L

200        LD   D,H ;Copy HL into DE

210        INC  DE ;DE is destination for LDIR

220        LD   (HL),0 ;Clear the leftmost byte

230        LDIR ;And copy into the other 32

240        POP  BC ;Get line counter back

250 ;

260        HALT ;A short delay.

270        DJNZ F_LOOP ;Loop until done

280        RET

290 ;

300 GETADD LD   L,B

310        SLA  L

320        LD   A,YTABLE/#100

330        ADC  A,0

340        LD   H,A

350        LD   A,(HL)

360        ADD  A,C

370        INC  L

380        LD   H,(HL)

390        LD   L,A

410        RET

420 ;

___________________________________________________________

                         Listing 5

  10        ORG #C000

  20        ENT #C000

  30 YTABLE EQU #FE00 ;Whatever lookup is

  40 ;

  50 ;These lines form a

  60 ;short test for the

  70 ;routine.

  80 ;

  90        LD   BC,#0208

 100        LD   DE,#1010

 110        CALL PIXSCR

 120        RET

 130 ;

 140 ;Pixel Scroll a window

 150 ;upwards...

 160 ;

 170 ;Entry B = ystart

 180 ;      C = xstart

 190 ;      D = Depth

 200 ;      E = Width

 210 ;

 220 ;All Parameters in

 230 ;Character Positions

 240 ;

 250 ;Exit A,BC,HL,IX

 260 ;     all corrupt

 270 ;

 280 ;     DE preserved

 290 ;

 300 PIXSCR EQU $

 310        LD   A,B ;Test start y

 320        CP   24  ;for bottom of screen

 330        RET  NC ;and abort if too big

 340        ADD  A,D ;also test base of window

 350        CP   24

 360        RET  NC ;and abort if too big

 370 ;

 380        LD   A,C ;similarly test x start

 390        CP   32 ;screen is 32 chars wide

 400        RET  NC ;abort if too big

 410        ADD  A,E ;and check right hand

 420        CP   32 ;edge of window

 430        RET  NC

 440 ;all the above tests could

 450 ;be cut out in a finished

 460 ;program where you are sure

 470 ;that the window size

 480 ;is always legal.

 490 ;

 500 ;

 510        LD   L,B ;pick up the y start

 520        LD   H,0 ;into HL

 530        ADD  HL,HL ;times it by 8 to

 540        ADD  HL,HL ;convert to a pixel pos

 550        ADD  HL,HL ;

 560 ;

 570        ADD  HL,HL ;and double this to

 580 ;index into the ytable

 590        EX   DE,HL ;switch to DE as no ADD IX,HL

 600 ;

 610        LD   IX,YTABLE

 620        ADD  IX,DE ;Point IX to the

 630 ;right place in the ytable

 640        EX   DE,HL ;get DE back

 650 ;

 660        LD   A,D ;now convert character

 670        ADD  A,A ;depth into number

 680        ADD  A,A ;of pixel lines

 690        ADD  A,A

 700        DEC  A ;we move one less than this

 710        LD   B,A ;put this counter into B

 720

 730 ;

 740 ;Now B = No of pixel

 750 ;        lines to move

 760 ;    C = X start pos of

 770 ;        the window

 780 ;    E = Width of the

 790 ;        window

 800 ;   IX   Points to the

 810 ;        position in the

 820 ;        Ytable

 830 ;

 840 ;the loop below moves each pixel line up 1

 850 PIXSC1 PUSH BC ;save these registers

 860        PUSH DE ;as they are corrupted by

 870 ;this loop

 880        LD   B,E ;get width into B for inner loop

 890        LD   A,C ;get byte position

 900        ADD  A,(IX) ;of start of this

 910        LD   E,A ;pixel line to

 920        LD   D,(IX+1) ;DE reg pair

 930        LD   A,C ;and byte address

 940        ADD  A,(IX+2) ;of start of line below

 950        LD   L,A ;into HL register

 960        LD   H,(IX+3) ;pair

 970 ;

 980 PIXSC2 LDI  ;copy (HL) to (DE)

 990        DJNZ PIXSC2 ;for width of window

1000 ;

1010        INC  IX ;step pointer to

1020        INC  IX ;next line

1030        POP  DE

1040        POP  BC ;restore both reg's

1050        DJNZ PIXSC1 ;back to move next line

1060 ;

1070        LD   A,C ;this is byte address of

1080        ADD  A,(IX) ;bottom left hand corner

1090        LD   L,A ;of the window

1100        LD   H,(IX+1) ;into HL

1110        LD   B,E ; width to B again

1120 PIXSC3 LD   (HL),0 ;fill bottom line

1130        INC  L ;with zero's

1140        DJNZ PIXSC3

1150        RET ;finished...