Task: Learn About Display List Interrupts in BASIC
Time: 1-2 hours
Atari was far ahead of its competitors when it was released with superior graphics. This was largely due to the ANTIC chip that served as a graphics processor with its own instruction set and program for drawing the screen (i.e. the display list). The display list makes it possible to mix graphics modes giving the programmer great flexibility for drawing screens with both text and graphics. Unfortunately, each graphics mode was limited to small number of colors despite the Atari having 256 total colors to choose from with the GTIA chip. For example, graphics 0 is a text mode that can display 40 columns by 24 rows of characters with only two colors for the background and the text. Graphics 0 is even more limited than that because the color of the text is dependent on the background color and thus can not be set independently. Fortunately, the Atari engineers built in a remedy for these restrictions. The display list interrupt (DLI) makes it possible to control the background color of every line drawn on the screen. Wikipedia generally refers to this method as a raster interrupt or horizontal blank interrupt. The Atari 8-bit home computers were the first to do this. Although I have used color as the motivating example, DLIs can be used for a number of line-specific special effects when combined with other things like player-missile graphics and characters sets. Some of these examples and additional technical details for programming DLIs in machine language can be found in this post on the Player-Missile website.
Learning about DLIs is difficult for several reasons. First, it is used in conjunction with other features of the Atari including the display list and vertical blank interrupt which each take time to learn and understand. Second, there are a number of details that must be taken care of in the right order to implement a DLI. Third, the code that runs during the interrupt to set the parameters of the next mode line must be written in assembly language since it is run during the horizontal blank which has precious few CPU cycles (e.g. ~30-40) and thus must be executed quickly. Fourth, the nom-maskable interrupt enable (NMIEN) hardware register which is used to turn on the DLI must be set during a vertical blank interrupt or VBI using assembly language. Using a POKE to do this in BASIC can results in the interrupt occurring halfway down the screen throwing off the timing of the DLIs. Trust me on this one! Finally, there is no one authoritative source on DLIs. Each source I have read explains it differently and leaves out key details that must be collated to have a complete understanding of the process. I have been playing with DLIs for a few weeks and will try to convey what I have learned. This was one of the more frustrating topics the first time through.
The first thing you need to know is that the ANTIC chip is busy working with the GTIA to draw the screen while the CPU is busy with the computations required by the software. This was a huge advance over the Atari 2600 which lacked a graphics chip resulting in a CPU burdened with drawing the screen one scan line at a time. The only time the 2600 CPU had to do in-game computations was during the horizontal blank (a brief pause) at the end of each scan line and the vertical blank (a longer pause) when the last of the 192 scan lines comprising the screen was completed. With the 8-bit computers, the ANTIC gets its instructions from its display list which is created automatically when a graphics mode is specified in BASIC or when a custom display list is created. With the display list in hand, ANTIC goes about its business drawing the screen within the limitations of each graphics mode it is asked to draw. The DLI essentially tells the ANTIC to signal to the CPU that there is an NMI enabled. This happens when ANTIC gets to the end of the current mode line. The CPU stops what it is doing and then runs, or services, an assembly language routine you provide at a specified memory address. Once the code is executed, the CPU goes back to what it was doing. Calling a DLI every mode line means that you can give each line a different color, for example, or implement other special effects.
I will step through here some example BASIC code and assembly code for implementing a DLI to set background colors on a line by line basis. Here is a text file for the BASIC code.
The fist thing we do in this BASIC example is set up a Graphics 2 screen and set the character color to white and the background color to a dark blue (color=112 or hexadecimal 70). This will serve as the color of the first two mode lines. We will set the colors of the rest with the DLI below. We then print the values from the display list for each mode line. A +128 indicates a mode line with a DLI called during its horizontal blank period. The first of these is mode line 2. Since the DLI fires during its horizontal blank (i.e. after the mode line is done being drawn), the color change doesn’t happen until mode line 3. For this reason, we can’t change the color of mode line 1. In my color example, I want to specify a gradual change in shades of color moving down the screen. Unfortunately, there are more mode lines in Graphics 2 than there are gradual shades for any one color. So, I decided to do one shade per every two mode lines. This is a long way of explaining why I set the background color to dark blue using the SETCOLOR statement on line 20. This serves as the color for the first two mode lines. The others are set by the DLIs on mode lines 2 through 11 (11 specifies the color for more line 12). A bit complicated! The figure below shows the final product with the DLIs.
10 REM *** GRAPHICS INITIALIZATIONS ***
20 GRAPHICS 2+16:SETCOLOR 0,0,14:SETCOLOR 4,7,0
30 POSITION 0,0:PRINT #6;”DL+3=7+64″
40 FOR I=6 TO 15
50 POSITION 0,I-5:PRINT #6;”DL+”;I;”=7+128″
60 NEXT I
70 POSITION 0,11:PRINT #6;”DL+16=7″
The next bit of BASIC code initializes the memory address of our display list in the variable DL (line 110). We will use this later to modify the DL to add the DLI instruction (+128). Line 120 initializes our mode line counter to 0 for our DLI assembly language routine which changes the background color on a line by line basis. This is stored in zero page memory address 203 which is easy to access from assembly. Line 130 initializes a string where we will store our vertical blank interrupt or VBI routine which will set up the interrupts during the vertical blank after the screen is drawn.
100 REM *** DL, DLI, & VBI INITIALIZATIONS ***
120 POKE 203,0
130 DIM VBI$(10)
Read in Assembly Code for Color Changes During the DLI, Setting the DLI and VBI Interrupts, and the VBI Initialization Routine
Line 220 reads in the assembly code called during the DLI to make the line by line color changes. The assembly code, in the form of mnemonics and their equivalent decimal values, is shown below with comments. The assembly decimal values are read in by BASIC on line 220 from the DATA statement in lines 1010 to 1030. The code is stored on page 6 of memory (location 1536=6 pages * 256 bytes per page) which can’t be overwritten by BASIC and is thus a safe place.
200 REM *** READ IN ASSEMBLY CODE FOR COLOR CHANGES ***
210 REM *** STORE IN PAGE 6 MEMORY LOCATION 1536+I ***
220 FOR I=0 TO 28:READ DAT:POKE 1536+I,DAT:NEXT I
1000 REM *** CODE FOR CHANGING COLORS DURING INTERRUPT ***
1010 DATA 72,138,72,166,203,189,40,6,141,10,212,141,26,208
1020 DATA 232,134,203,224,10,208,4,169,0,133,203
1030 DATA 104,170,104,64
The assembly code for the line by line color change during the DLI is shown below. We will tell the DLI where to find this code later (line 710). An important thing to note is that this code is executed every time a DLI is called (e.g. mode lines 2 through 11 for color on lines 3 through 12). Thus, we need a way to keep track of what line the code is being executed on so we can specify the correct color. I do this by setting up a mode line counter value in page zero memory location 203 which we initialized to zero above on line 120. This memory location is incremented each time the code is called and then reset to 0 when we have reached our limit of 10 mode lines executed (only 10 since the first and last do not need DLIs). This mode line counter is used as an index to the color values for each mode line which are store in page 6 memory locations 1576 to 1585. Finally, we need to write (any value) to the WSYNC register (address 54282 or page 212 high byte with low byte 10) which tells the Atari to wait for the horizontal blank before executing any more code. This makes sure the color change doesn’t happen while the mode line is being drawn and is why the color change is executed on the next mode line down. In summary, the middle part of the code shown below loads the mode line counter, uses it to load the next color value, issues the WSYNC, loads the color value into the background color register, increases the counter, checks to see of the counter has exceeded the number of mode lines we want to change, and then resets the counter to 0 if we are ready for the next screen to be drawn. Otherwise, it exits and then executes on the next mode line. The first few lines of the code below save the registers and then at the end restores them before returning to BASIC. The specifics of each instruction are in the comments below. Note that we are likely pushing up against the limit of how many cycles we can execute during the DLI. Each instruction takes 3 or 4 cycles to execute. For example, PHA takes 3 cycles while PLA takes 4. The zero page LDX takes 3 while the absolute index LDA takes 4. The code does work in this example, but something to keep in mind as you write DLI code.
This next section of BASIC code reads in our assembly program (decimal values in DATA statement on line 2010) to enable the DLI and VBI interrupts during the vertical blank period. To review, we need to enable the interrupts during the vertical blank or run the risk of a BASIC POKE command enabling the interrupts while the screen is being drawn thus throwing off the timing of the mode line where each DLI executes. I have seen this happen every single time I have tried to write a DLI example using a POKE in BASIC instead of VBI routine. We save the decimal values of the assembly program to page 6 of memory (locations 1565 to 1573). We will point our VBI routine to this memory address later.
300 REM *** READ IN ASSEMBLY CODE TO ENABLE INTERRUPTS ***
310 REM *** STORE IN PAGE 6 MEMORY LOCATION 1536+I ***
320 FOR I=29 TO 37:READ DAT:POKE 1536+I,DAT:NEXT I
2000 REM *** CODE FOR ENABLING INTERRUPTS DURING VBI ***
2010 DATA 169,192,141,14,212,76,98,228,96
Below are the assembly mnemonics and corresponding decimal values to enable the interrupts. We first load the accumulator with value 192 and then write it to the NMIEN register at address 54286 (high byte =212, low byte = 14). This would be implemented in BASIC as POKE 54286,192. We then exit the deferred vertical blank with a JMP to address XITVBL (58466 with high byte of 228 and low byte of 98).
Next we need to read in the assembly code for setting up the VBI to call and execute the interrupt code (just described above) each and every vertical blank. We don’t need to put this code on page 6 because it can be executed in BASIC using the USR command. So, we load this code into a string (VBI$) and let the Atari keep track of its memory location.
400 REM *** READ IN ASSEMBLY CODE TO INITIALIZE VBI ***
410 REM *** STORE IN STRING VBI$ TO BE CALLED BY USR ***
420 FOR I=1 TO 10:READ DAT:VBI$(I)=CHR$(DAT):NEXT I
3000 REM *** CODE FOR INITIALIZING VBI ***
3010 DATA 104,169,7,160,29,162,6,76,92,228
Here is the assembly code for setting up and initializing the VBI. This has been described in more detail in a previous post. Briefly, we load the X register with a 7 to indicate we want to use the deferred VBI. We then load the high byte (6) into the X register and the low byte (29) into the Y register. These point to the assembly code for executing the DLI and VBI interrupts described above. We then jump to the SETVBV address (58460 with high byte 228 and low byte 92) which sets up the deferred VBI given these parameters. Once this happens, the interrupt routine from above will be executed each and every vertical blank after the screen has been drawn.
Read Colors to Set During the DLI
Next we need to read our 10 color values into memory for use by our assembly routine which will set the background color on each mode line. These are stored one page 6 of memory at locations 1576 to 1584. Recall that our assembly code above reads these using an LDA 1576,X where X is our counter index initialized to zero. This steps through each color value in memory as the counter increases.
500 REM *** READ IN COLORS TO DISPLAY ON EACH LINE ***
510 REM *** STORE IN PAGE 6 AFTER ASSEMBLY CODE ***
520 FOR I=40 TO 49:READ DAT:POKE 1536+I,DAT:NEXT I
4000 REM *** COLORS TO DISPLAY ***
4010 DATA 114,114,116,116,118,118,120,120,122,122
Modify the Display List for the DLIs
Almost there! The last bit of preparatory work we need to do is to modify the display list so it knows which mode lines we want to call a DLI for executing our assembly code which sets the background color. Recall the first mode line (DL+3) doesn’t get a DLI because we want to color pairs of mode lines. The second mode line (DL+6) gets a +128 telling it to execute the DLI. Because of the WSYNC, this code gets executed during the horizontal blank after the mode line is draw with the end result of this color value showing up on mode line 3. This continues to mode line 11 (DL+15) which specifies the color for mode line 12. Mode line 12 doesn’t need a DLI because there is no mode line to color after it so we leave it alone to just specify ANTIC mode 7 (Graphics 2).
600 REM *** MODIFY DISPLAY LIST FOR DLI WITH +128 ***
610 FOR I=6 TO 15:POKE DL+I,PEEK(DL+I)+128:NEXT I
The Main Loop
We finally made it to the main loop! That was some work to change the background color of a few lines. We have two remaining tasks to take care of before we can reap the rewards of our hard work. First, we need to tell the Atari where our DLI code to be executed resides. Remember that we loaded this code into the beginning of page 6 of memory. We POKE the high byte (6) to memory location 512 and the low byte (0) to 513. The DLI will look here when it is ready to execute. The last thing we need to do is to execute our code to set up the VBI which will in turn execute the code to enable the DLI and VBI interrupts. This is done with the USR command using ADR to point to the memory location of the code stored in string VBI$. We end with an endless loop so the screen stays on. This loop endlessly executes our DLI code 10 times per screen during the horizontal blanks for mode lines 2 through 11 and the VBI code once per screen during the vertical blank.
700 REM *** SHORT & SWEET MAIN LOOP! ***
710 POKE 512,0:POKE 513,6
730 GOTO 730
Here is the ATR file and a text file with the BASIC code the DLI demo. This is a DOS 2.0S disk image. The filename of the BASIC code is DLI.BAS. Here are the text files for the DLI, interrupt, and VBI assembly code described above.
Open Altirra and load BASIC. I used Turbo BASIC XL but any flavor of BASIC compatible with Atari BASIC will do. You can also load the built-in BASIC from Altirra from the File->Attach special cartridge menu.
Open the BASIC code in your PC text editor or web browser and copy the text to your clipboard. Click on the View tab of Altirra and choose the paste text option from the bottom of the list. Altirra will slowly paste the text into the BASIC command line. Note you can go to the System tab and select “Warp Speed” for fast pasting. It is important to keep focus on the Altirra window or the paste will stop and it will lose some characters.
Also try booting the ATR file in Altirra or on your Atari from your PC using SIO2PC. You can also load the ATR on a FujiNet or similar device and boot from there. You must have a BASIC cartridge loaded. You can load the file from BASIC using LOAD “D:DLI.BAS” and then type RUN.
There are a number of sources covering DLIs. Tutorial part 4 in the December 1981 issue of BYTE covers DLIs. There are a number of other sources of information about DLIs. Here is the De Re Atari chapter. Here is one from Compute! (1984). Here is a chapter from The Creative Atari book. Here is a chapter from Compute’s Second Book of Atari. Here is a chapter from the book Assembly Language Programming for the Atari Computers. Here is one from ROM Magazine (1983). Here is a tutorial from a user on the web. Here is another web tutorial. Here is an in-depth tutorial from the Player-Missile website which assumes familiarity with assembly programming for the Atari.
Feeling comfortable with DLIs took some time. The sources above all have useful information but they can sometimes conflict with one another or confuse some of the concepts. I learned mostly by trial and error until I got a set of methods down which I feel I can use consistently to implement DLIs. I would say the single most important lesson I learned was to use the VBI routine to set the interrupts. Once I got this down I started getting good results when using DLIs for graphics tricks. I have tried to pass on what I learned above. Feel free to contact me with questions or comments and suggestions. I am sure I have left something important out.