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 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...