There are *basically* (but not only) two types of AL programs formats you can write on the TI, programs that run from the Editor/Assembler (EA) menu 3 option, commonly known as EA3. These types of programs require a linking phase to finalize the memory references before execution. Because of this, this kind of code is "relocatable" and the loader can place the program just about anywhere in memory that it will fit.
The second type of program is one meant to be run from a cartridge ROM. These programs are set up differently and have a few more restrictions as to defining labels and such. For now we will stick with EA3 types of programs.
This is the basic skeleton:
DEF START * Typically EQUates, DATA, and BYTE defintions for variables * will be at the head of the program, with longer data sets * places at the bottom of the code. * Program execution starts here START LIMI 2 LP9999 JMP LP9999 END
This program will do nothing but execute an endless loop. However, interrupts were enabled with LIMI 2, so the FCTN= combination can be used to reset the console.
Is an assembler directive, which means it will be read and used by the assembler. It is *not* assembly language and does not cause any code to be created. This directive is used to place a label in the REF/DEF table that the linker uses to load and find programs. This entry is saying that there will be a label called "START" and that is where our program will begin execution. "START" could be anything we want that is 1 to 6 characters in length.
Let's add something that will actually do something for us like set the VDP to Graphics Mode I, clear the screen, and say "Hello World". I think this is the basic entry level program that most people try to start with.
Sometimes you will see a $-2 or such used in code. The assembler users the $ to represent the current location and will subtract (or add) the specified number of bytes to the current location when generating an address. This is usually used as a short cut for short loops to avoid using a label. I do not currently recommend using this method because I have found that Asm994a gets it wrong, and as of right now that is the only Windows based assembler I know of. If you are going to stick 100% with the E/A, then go ahead, but don't be surprised if you try to assemble with Asm994a and your code does not work.
DEF START * VDP Memory Map * VDPRD EQU >8800 * VDP read data VDPSTA EQU >8802 * VDP status VDPWD EQU >8C00 * VDP write data VDPWA EQU >8C02 * VDP set read/write address * Workspace WRKSP EQU >8300 * Workspace R0LB EQU WRKSP+1 * R0 low byte reqd for VDP routines * Program execution starts here START LIMI 0 LWPI WRKSP LIMI 2 LP9999 JMP LP9999 END
First notice that there are no REF statements for the normal VDP routines. This is because I don't like using the console based routines for a couple of reasons:
1. They are slow since they were designed to save ROM space instead of being fast to execute
2. They use BLWP which is slow and designed primarily for context switching in a multi-tasking system like TI's mini computers where the TMS9900 CPU was designed to be used
3. The routines use a workspace in 8-bit RAM! This in of itself should be a sin!
Also, I don't like to use the GLP routines because they require a workspace pointer in the 256 bytes of scratch pad RAM. Since there is only 256 bytes of 16-bit RAM in the machine, it is highly prized memory and I don't like some other console routines chewing up 32 bytes of that RAM. Anyway, the VDP routines provided in the E/A cartridge are slow, slow, and slow, and I'm a speed freak.
What we set up in the code is an equate (EQU) to the memory address where the VDP is mapped in the 99/4A's address space. Equates are assembler directives that simply let us use a label instead of a number. The assembler will do a search and replace on the labels, so any place you see VDPRD, for example, will be replaced with >8800.
We have also changed the first instruction to LIMI 0, which will disable the VDP interrupt, which in turn disables the console ISR. While the VDP interrupt is a nice thing to have to use as a sixtieth of a second timer in a game, it triggers the console's one and only interrupt service routine which we usually don't want running (it tries to do too much IMO.) So, we shut it off. We can still use the VDP interrupt, we just have to poll for it, which is not too bad.
The next thing we do is LWPI (load workspace pointer immediate), which sets the workspace pointer to >8300, which is the first address in the 16-bit scratchpad RAM. Since the registers in the 9900 CPU are memory-based, you absolutely positively always want to keep the workspace pointer set to an address in that 16-bit RAM (unless you really want to kill performance, in which case you should just use XB... )
Finally you will note an equate called R0LB. This creates a label that gives us a convenient way to access the low byte of R0 without using SWPB. It comes in handy when dealing with the VDP as you will see in the routines we set up.
Now let's do something, like clear the screen:
DEF START * VDP Memory Map * VDPRD EQU >8800 * VDP read data VDPSTA EQU >8802 * VDP status VDPWD EQU >8C00 * VDP write data VDPWA EQU >8C02 * VDP set read/write address * Workspace WRKSP EQU >8300 * Workspace R0LB EQU WRKSP+1 * R0 low byte reqd for VDP routines * Program execution starts here START LIMI 0 LWPI WRKSP CLR R0 * Set the VDP address to zero MOVB @R0LB,@VDPWA * Send low byte of VDP RAM write address ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) MOVB R0,@VDPWA * Send high byte of VDP RAM write address LI R1,>2000 * Set high byte to 32 (>20) LI R2,768 * Set every screen tile name to >20 CLS MOVB R1,@VDPWD * Write byte to VDP RAM DEC R2 JNE CLS LIMI 2 LP9999 JMP LP9999 END
For now we are doing things directly, but soon we will make a subroutine to encapsulate the VDP reading and writing. One of the most important things to understand about the 9918A VDP is that it maintains an internal address register that auto-increments any time a byte of data is written-to or read-from the VDP. This is handy since setting the VDP's address register takes two writes to the VDP write-to-register port. You can see that above. R0 is loaded with the address we want to set. In this case we are going to set up the address of zero, since I know the VDP name table defaults to memory location zero, we are setting up the VDP address to write bytes to the VDP RAM used to display the screen. A little later we will set the name table location directly so we know without a doubt where it is located in the VDP RAM.
The other thing to know about the VDP registers is that they are read only (except for the status registers.) So the only way to know for sure where the various VDP tables are located is to set them.
So, the address is set up and written to the VDP. The VDP's internal address register is 14-bits since it has to reference up to 16K, so it takes two 1-byte transfers to load the address register. After that, any data we write (or read) to the VDP will cause the address to auto-increment after the write (or read.)
Next we load R1 with the byte we want to write to the VDP. When writing from registers to memory mapped devices, the MSB of the register will always be transferred. Thus, the low byte of R1 really does not matter, but we use >00 to help keep things clear. Since we are clearing the screen, the pattern for tile (character) 32 is already defined as space thanks to the console boot-up process (ASCII 32, or >20 in hex.) We set R2 to 768 to keep track of how many bytes we have written to the VDP. We count *down* to take advantage of the fact that the 9900 CPU will compare R2 to 0 after the DEC instruction and we can use that to control the JNE (jump if not equal) instruction. So we are saying, Jump if R2 is not 0.
Finally we re-enable interrupts and cause an infinite loop. Something else to note. You *MUST* disable interrupts when reading or writing to the VDP because the console ISR also reads and writes the VDP and will mess up the address you had set up. And since your program has no way to know that it was possibly interrupted, your VDP accesses will be all messed up. But, we don't need the console ISR, so we will just leave it disabled until the end, if we ever enable it at all.
Okay, that's is for this installment. Next I'll add code for a full VDP mode setup so things are exactly where we want them, and we can set up the VDP subroutines.
Edited by matthew180, Tue May 11, 2010 9:37 PM.