Exploring the ATARIĀ® Frontier

Display List Interrupts by Alan J. Zett

Editor's Note: This is the fourth part of a series. To avoid confusion, we advise that you read the first three installments (SoftSide, May 1982, issue 31, and issue 34) before reading this one. A solid background in BASIC, as well as the rudiments of assembly language, are also recommended.

A Different Tune

Now that we have covered the subject of modified Display Lists (DL's) in the last three installments, we can approach underlying concepts that make some of the most spectacular visual effects. If the capabilities presented for custom display lists have appeared interesting, the options available while using a modified display list will astound you! As the months unravel, we'll see that once you've unlocked the power stored in the ANTIC chip, programs can be written which far exceed the limited ability of BASIC. When short and simple Machine Language routines are added to BASIC, the quality of the software produced improves substantially.

New Business

Our topic, this time, is Display List Interrupts (DLIs). The DLI is an option that is available in any of the ANTIC Character, Graphic, or Blank mode line instructions. When ANTIC is processing the display, it sends information to CTIA (GTIA in newer machines) for every line on the screen. If you remember, each line is known as an ANTIC mode line. A mode line in turn is a group of television scan lines that, when combined, represent a visual line of display text or graphics. After the video display is composed, it is then sent to the TV or monitor where each mode line is traced on the screen, one scan line at a time. To the human eye, all the scan lines appear to be on the screen at once.

To the computer, however, there is a time delay before each mode line is drawn on the screen. There is actually enough time to execute a small number of Machine Language instructions before the next mode line is drawn. The length of this code is limited to the number of cycles each instruction takes of the CPU's time. On the average, there is enough time for a few minor, nonrepetitive operations. All that we need is some way to check the normal flow of CPU processing in order to divert some of its precious time to a simple task. This is the function of the DLI: to interrupt the normal processing of the DL.

Once we have the DL's attention, what can we do? We can change the color registers in between individual mode lines on the screen. This is the technique used in this month's Atari version of Hopper. In fact, we will be dissecting the DL routines from that program later. A form of animation could be accomplished by flipping the DL's pointer to screen memory data between different areas of memory. Player/Missile Graphics (PMG) could be moved or have their colors change. The list is endless and limited only by the amount of time it takes to execute the instructions. Sound effects could also be generated, although a complicated task such as music or graphic animation would require more time. The Vertical Blank Interrupt (VBI) is provided for this purpose. The main difference between DLIs and VBIs is that the DLI can execute between mode lines whereas the VBI executes between each complete screen. We'll be covering the VBI in a later installment.

Yet Another DL

Figure 1 contains a list of some important memory locations when using DLIs. We can use it in conjunction with Listing 1 to discover how to create a working DL with interrupts. Listing 1 is the routine to create the DL from Atari Hopper. We'll go over that in detail in just a moment. Figure 2 is a representation of the DL generated by Listing 1. It justifies a little discussion because of the use of the DLI option.

Figure 1: Table Of Important Memory Locations


00087$0057DINDEXCurrent GRAPHICS mode of screen.
00512$0200VDSLSTLSB of Display List Interrupt routine.
00513$0201VDSLSTMSB of Display List Interrupt routine.
00559$022FSDMCTLControl of video display and PMG.
00560$0230SDLSTLLSB of Display List.
00561$0231SDLSTHMSB of Display List.
01664$0680USRAM128 unused bytes of user RAM.
54282$D40AWSYNCWait for horizontal sync register.
54286$D40ENMIENNon maskable interrupt enable.

Figure 2: A custom DL with interrupts generated by listing 1 (from Atari Hopper).

DL Byte #Byte ValueMode Type

04198GRAPHICS 1 w/LMS and DLI options.
05nnnLSB of screen memory data.
06nnnMSB of screen memory data.
07066GRAPHICS 0 w/LMS option.
08nnnLSB of next line of screen data.
09nnnMSB of next line of screen data.
17130GRAPHICS 0 w/DLI option.
20130GRAPHICS 0 w/DLI option.
27130GRAPHICS 0 w/DLI option.
32065Jump w/WVB option.
33nnnLSB of Display List.
34nnnMSB of Display List.

The DL for Hopper is only 34 bytes long. Unlike the Load Memory Scan (LMS) option, which requires two extra bytes for a 16 bit screen memory data address, the DLI is a stand alone instruction. ANTIC is smart. It knows that whenever it processes a character or graphics mode line, only the lower four bits (or nibble) are used. The higher four bits should be reset (equal to zero) on a normal instruction. The exception is that a blank mode line instruction requires that the low nibble be zero; that is how it is distinguished from a character or graphics mode line. If, however, ANTIC detects a non-zero high nibble, it will recognize that a mode line option was specified, as long as the corresponding low nibble is not zero. Blank mode lines use the lower three bits of the high nibble (bits four, five, and six) to determine how many blank scan lines to skip . On a character or graphics mode line, if bit six is set (equal to one), the LMS option is executed. If bit seven is set, ANTIC interprets this as the DLI option. This means that although the blank mode line can use the DLI option, it cannot use the LMS option. The LMS option would be interpreted as part of the number of scan lines to skip. In a character or graphics mode instruction, you can set both bits six and seven at one time. This was done in the first visible mode line of the Hopper DL.

If you look at Figure 2, you will see that the first three bytes of the DL are blank mode lines. This is a standard technique used to bring the start of the video display down 24 scan lines (Each blank mode line instruction is set for skipping eight scan lines.) from the top of the video signal's trace pattern to make sure all of it can be seen. Bytes 4 through 6 accomplish two tasks. They set the pointer to the top of screen memory data for the first visible mode line, and also call the first DLI to modify the background color of mode line two. Bytes 7 through 9 adjust the pointer to screen memory data for mode line two. The reason for this will be explained later. Bytes 10 through 31, with the exception of bytes 18, 20, and 27, are all standard GRAPHICS 0 mode lines. Bytes 18, 20, and 27 are also GRAPHICS 0, but each of these has bit seven set (128 added to the normal value) to generate a DLI. Byte 32 is the standard Jump instruction with bit 6 set (64 added to the base value). Jump instructions are totally separate from the other three kinds. Setting bit 6 will cause the Wait for Vertical Blank (WVB) option to execute. This will cause the DL to start processing again at byte number one, but only after the video display has had a chance to bring the scanning beam up to the top of the display. Now that we have walked through the DL itself, we can show how it can be generated from BASIC.

Take Apart Time

Listing 1
10000 N0=0:N1=1:N2=2:N3=3:N4=4:N5=5:N6=6:N7=7:N8=8:N9=9:N10=10
11000 GRAPHICS 21:POKE 752,N1:C=N0:POKE 87,N0
11010 DL=PEEK(560)+PEEK(561)*256+N4
11020 MEM=PEEK(DL)+PEEK(DL+1)*256+40
11030 MH=INT(MEM/256):ML=MEM-MH*256
11040 POKE 559,N0:POKE DL-N1,198
11050 POKE DL+N2,66:POKE DL+N3,ML
11060 POKE DL+N4,MH
11070 FOR J=N5 TO 26:POKE DL+J,N2:NEXT J
11080 POKE DL+12,130:POKE DL+15,130
11090 POKE DL+22,130:POKE DL+27,65
11100 POKE DL+28,PEEK(560)
11110 POKE DL+29,PEEK(561)
11120 READ A:IF A=999 THEN 11140
11130 POKE 1744+C,A:C=C+N1:GOTO 11120
11140 POKE 512,208:POKE 513,N6
11150 POKE 1774,176:POKE 1775,180
11160 POKE 1776,N0:POKE 1777,144
11170 POKE 1778,N0:POKE 54286,192
11180 POKE 559,34
11190 DATA 72,138,72,174,242,6,189,238,6,141,10,212,141,24,208,232
11200 DATA 224,4,144,2,162,0,138,141
11210 DATA 242,6,104,170,104,64,999

Line 10000: Sets numeric constants. Variables are used to speed up execution time and cut down on program size. Every time you enter a number into your program, it is stored in memory as a six byte floating-point number. That means that the repetitive use of numbers such as 0 and 1 in a BASIC program takes up six bytes. After a while, this can really eat up storage space. On the other hand, if you assign a variable the value of say, zero, and use it instead, every number will take up only the number of characters required to specify a variable. How many bytes does it take, you ask? Only one! Each variable, no matter how many characters long, is stored as a relative byte position in a lookup table.

Line 11000: Set up a full screen GRAPHICS 5 display. Here we have violated one of the first rules I set in past installments. When creating a modified DL, we should choose the GRAPHICS mode in the modified DL that takes up the most memory. If you look at Figure 2, you will see that nowhere in the DL are there any GRAPHICS 5 mode lines. By rights, we should have used a GRAPHICS 0 DL to modify. The explanation is a little complicated. In the case of Hopper, which uses a GRAPHICS 1 mode line at the top followed by 23 mode lines of GRAPHICS 0, the normal problems associated with having a GRAPHICS 1 mode line by itself had to be overcome. Normally, when you add a mode line such as GRAPHICS 1 which uses 20 screen bytes to a mode line such as GRAPHICS 0, which uses 40, you must use two of them so that the visible GRAPHICS 0 lines again line up with the left edge of the screen. (See SoftSide issue 31.) This could not be done in Hopper because all 23 GRAPHICS 0 mode lines were needed. To get around this, the DL was forced to ignore the missing 20 bytes by resetting the pointer to screen memory data 20 bytes further down the screen. By moving the actual start of the next mode line's screen memory data down twenty bytes, the wrap around that usually occurs as a side-effect was defeated. This is done with the LMS option, and requires more than one DL byte, thereby changing the length of DL into which it is inserted. Padding the screen with an extra GRAPHICS 1 mode line does not change the length of the DL. Using a GRAPHICS 0 screen with a DL that was longer than the original would be a complicated procedure. Enter GRAPHICS 21 ! This version of the GRAPHICS 5 mode uses the same amount of screen memory data as GRAPHICS 0, but has more mode line bytes in the DL with which to work. With extra space provided, we could POKE in a GRAPHICS 0 DL with enough room left to use the second LMS option. The result was a display that cannot be duplicated with normal procedures. The POKE 87 tells the Operating System (OS) that we are no longer in GRAPHICS 21.

Line 11010: The variable DL is set to the start of the DL plus four.

Line 11020: MEM is set equal to the start position of the screen memory data of the second mode line. This is used with the LMS option to skip the unused 20 bytes from the GRAPHICS 5 mode line which will be POKEd in later.

Line 11030: MH equals the MSB of MEM while ML equals the LSB of MEM.

Line 11040: POKE 559,0 shuts off the screen to speed up Atari BASIC and to hide the changing display from the user. POKEing DL-l sets the top visible mode line of the DL to GRAPHICS 5 with the mandatory LMS option for the start of the screen display, as well as a DLI option. The LMS option is specified by adding 64 (set bit 6) to the DL mode byte.

Line 11050: POKEing DL + 2 with 66 sets the second visible line of the display to be a GRAPHICS 0 line with the LMS option. ML is POKEd into the low screen byte position of the LMS option.

Line 11060: POKEs in the high screen byte of the LMS option.

Line 11070: POKEs in all of the remaining GRAPHICS 0 lines that will be used. Note that some of these locations will be modified.

Line 11080: POKEs in two GRAPHICS 0 mode lines with the DLI option. When ANTIC encounters a DLI option, it responds by generating a Non Maskable Interrupt (NMI). Every time an NMI is encountered, the CPU jumps to the OS NMI handler routine. OS then checks the status of the Non Maskable Interrupt ENable register (NMIEN) which is preset to select what types of NMIs will be processed. It is actually possible to force the Atari to ignore NMIs. If the interrupt originated in the area of memory occupied by the DL, and the DLI enable bit of NMIEN is set (bit seven), a jump to the routine specified by the DLI jump vector at memory locations 512 and 513 is executed. This pointer must be set to the start of the programmer's DLI routine. It is interesting to note that the DLI option must be specified in the mode line above the visible mode line you wish to interrupt. The interrupt routine will not start to execute until half of the mode line has been drawn. This is caused by delays in the OS for processing. By starting one mode line above, the interrupt will begin processing just before the visible screen line we wish to modify. This means that in order to modify the top visible line of the screen, the DLI must occur in one of the Blank mode line instructions. There is a problem, however. The interrupt will always start near the middle of the mode line in which the DLI option was specified. This means that if you change the background color in your DLI routine, it will not start at the leading left-hand side of the screen, but rather, somewhere in the middle. In addition, the break in the visible screen line will zigzag back and forth because of timing variations between each processing of the display. The only way to overcome this is to use a special memory location known as WSYNC. This location, when written to, will tie up the CPU until the start of the horizontal sync retrace scan. This will assure you of starting your routine at the beginning of a visible screen line. Simply writing anything to this location will cause it to function. This method was used in the assembly language DLI routine used in Hopper.

Line 11090: POKEs in the remaining GRAPHICS 0 DLI mode line and sets up a Jump instruction with the WVB option.

Lines 11100-11110: Provides the address needed for the Jump instruction to process.

Lines 11120-11130: POKE the DLI handler routine into locations 1744 ($06D0) on up. When the end of data is found (999), jump to line 11180.

Line 11140: POKEs the address of the DLI handler routine into the DLI jump vector VDSLST at locations 512 ($0200) and 513 ($0201).

Lines 11150-11160: Set up the color lookup table used in the DLI handler routine.

Line 11170: Sets the color table index to the start and enables the DLI interrupt vector by POKEing 192 into NMIEN.

Line 11180: Turns the screen back on after all the work is finished.

Lines 11190-11210: Contain the decimal values of the assembly language DLI handler routine.

And Now ... The 6502

Listing 2
                 0100        *= $06D0      ;SET START OF ASSEMBLY.
                 0110 ;
                 0120 ; DEFINE SYSTEM EQUATES
                 0130 ;
                 0140 WSYNC = $D40A        ;WAIT FOR HORIZONTAL SYNC REGISTER.
                 0150 BGRND = $D018        ;BACKGROUND COLOR HARDWARE REGISTER. 
                 0160 ;
                 0170 ; START OF DLI HANDLER ROUTINE
                 0180 ;
06D0: 48         0190 START PHA            ;SAVE THE A REGISTER ONTO THE STACK.
06D1: 8A         0200 TXA                  ;PUT X REGISTER INTO A AND THEN
06D2: 48         0210 PHA                  ;SAVE IT ONTO THE STACK ALSO.
06D3: AE F2 06   0220 LDX STORE            ;GET THE CONTENTS OF STORE INTO X.
06D9: 8D 0A D4   0240 STA WSYNC            ;WAIT FOR TIMING, THEN PROCEED.
06DF: E8         0260 INX                  ;POINT X TO NEXT COLOR FOR DLI USE. 
06E0: E0 04      0270 CPX #$04             ;IF WE'RE NOT PAST THE TABLE'S END,
06E2: 90 02      0280 BCC OUT              ;THEN GET READY TO LEAVE,
06E4: A2 00      0290 LDX #$00             ;ELSE POINT X TO THE TABLE'S START.
                 0300 ;
                 0310 ; END DLI, SET UP FOR NEXT
                 0320 ;
06E6: 8A         0330 OUT TXA              ;PUT X BACK INTO THE A REGISTER.
06E7: 8D F2 06   0340 STA STORE            ;SAVE UPDATED COLOR TABLE POINTER. 
06EA: 68         0350 PLA                  ;REMOVE OLD X REGISTER.
06EB: AA         0360 TAX                  ;PUT IT BACK IN PLACE.
06EC: 68         0370 PLA                  ;RESTORE OLD A REGISTER.
06ED: 40         0380 RTI                  ;EXECUTE RETURN FROM INTERRUPT.
                 0390 ;
                 0400 ; DLI COLOR TABLE AND POINTER
                 0410 ;
06EF: 00 00      0420 TABLE .WORD $00      ;RESERVE 4 BYTES FOR COLOR TABLE.
06F1: 00 00      0430 .WORD $00 
                 0450 .END

That's all there is to setting up the DLI display, but without a DLI handler routine, it won't work. Even though the source code provided in Listing 2 is commented, there are some techniques used in it that require explanation. After all, a DLI is only the means of making a fancy display. If you want to do something useful with a DLI, you have to do it yourself. That is the major advantage of the DLI - the ability to add your own DL processing commands to those already provided by ANTIC.

Timing considerations in the processing of a DLI routine will allow only 15 machine cycles before the start of the horizontal sync and about 11 cycles between the start of the horizontal sync and the appearance of the first electrons of the scan line. (Each 6502 instruction, or OPCODE, takes a certain number of clock cycles to execute. For more information on the cycle times of Machine Language OP-CODES, look in any good quality 6502 programming book.) That means that you can use about 15 cycles before using the WSYNC location, and about 11 more after the CPU restarts. In realistic terms, this means that changing more than three color registers at one time is impossible. However, more than one DLI can be used. This can be a problem, however, because there is only one DLI jump vector. If more than one DLI is used, the DLI handler must be able to figure out what interrupt it is currently processing. Luckily, there are many ways to accomplish this task.

If the DLI routine must do the same thing with different parameters or values each time it executes, a simple table and pointer can be used to tell it what value to use. This is the method used in Hopper. There are four DLI's with which the DLI handler must cope. The process of changing the color register is common to each DLI - only the color used must be changed at each mode line. All the Hopper DLI routine does is hold a table of colors in memory that will constitute the desired display. By keeping track of a pointer within the table, the routine knows exactly which color to use each time it is called.

But take, for example, a display with three DLI' s. The first must change the color of the screen; the second must move the position of a PMG; and the third modifies a DL pointer to screen memory data. Each routine is distinctly different, but there is only one DLI jump vector. How can the other two routines be used? There are two ways. If the routines are very short, a general menu DLI routine can be set up with a table of all the DLI routines. By keeping a pointer to specify which routine is currently required, the menu can branch to the location of the routine as shown in the table.

This method, however, eats up a lot of the CPU time in computing and branching to the proper DLI routine. Another method is to simply set up the address of the first DLI from BASIC, and after the DLIs are enabled, make each DLI routine modify the DLI jump vector to point to the next routine. This requires that each DLI routine knows the memory location of the next routine to be executed, and puts this address into the DLI jump vector at 512 and 513, just before finishing. This method is very effective and simplifies the processing of separate DLIs.

Basic Pitfalls

The first thing any DLI routine must do is save all of the registers it will use on the stack or in some other memory location (the stack is fastest). After it is finished processing, it must also restore the registers to their original condition by removing them from the stack. This is because the OS does not save any registers other than the Program Counter (PC). In effect, the OS never sees the interrupt occur, and as such, has no reason to suspect that any of the registers it was using may have been modified. Needless to say, this could cause grave problems in the OS, hence the need to save the registers.

Second, the programmer must be aware that the shadow registers in RAM (such as 710 for the background color) are written to the hardware registers in GTIA only during the VBI. For example, the Hopper DLI routine has to change a color register. Since the VBI occurs between the end of one screen display and before the next one is drawn, all of the DLIs will change the color register before the VBI is executed. If the DLI used shadow registers, only the last color change would be written to GTIA during the VBI because the other color changes would be overwritten before the VBI could look at them. For this reason, whenever you wish to modify a parameter during the DLI that is shadowed into RAM, you must write directly to the hardware register in GTIA. This also has the benefit (or disadvantage) of having the normal shadow register restore the modified parameter to its original state at the start of each screen. The OS and hardware technical manuals from Atari cover all of the shadow and hardware register locations in detail.

The only noticeable side-effect of using DLIs is that there is a small problem with the OS keyboard click routine. Whenever a keyboard character is pressed, OS causes the Atari's internal speaker to click. The OS times the clicking by using the WSYNC location. Since we also use this location and process our DLI routines with the expectation of being somewhere in the middle of a scan line when the DLI occurs, typing a key can sometimes cause the screen to jump or flicker. This is because when OS uses the WSYNC location, and we also use it in our routine, the DLI starts executing one line too late. Also, ANTIC and CTIA are confused because the CPU is tied up for a critical segment of time. The jumping is not terrrible, although it can be annoying. The only direct way around this is to not accept any keyboard input. The keyboard can be disabled with a few POKEs but the problem is not really bad enough to warrant the loss of keyboard entry. Take a look at the Pokey Player Editor (SoftSide, issue 34). It jumps a little, but not enough to distract you.

DLI Fini

These are most of the fundamentals of the DLI. There will always be certain situations that require special solutions, but the material presented so far should cover almost any practical application. We can now sit back and relax as the information slowly disappears from our minds. I would suggest re-reading this article again at a later date to help refresh the large amount of information presented. I am sure that this article will generate some questions, and I am currently compiling a list for a future installment. That's it for this time. Now is a good time to sit back at your computer and experiment with the concepts presented. Learning should be a fun experience, not a frustrating one. Take the time to play with your computer. It 's the only way to find out what it truly means to Explore the Atari Frontier.