Opry99er

**Clearing the Screen**
by: Matthew Hagerty

Clearing the screen is one of the first things any program needs to do when it starts, simply to have a known “clean slate” to start with before drawing on the screen.  The 9918A VDP has 4 graphic “modes”, with Graphics I being the most popular mode, so that is the mode used in this tutorial.
Graphics I mode sets the screen into a grid of 32x24 characters for a total of 768 screen positions.   Thus, clearing the screen consists of writing a “space” character (ASCII 32) to each of the 768 positions.  This example will provide all the code necessary to directly interface with the 9918A VDP so you can see what is all really going on.
       DEF  START

VDPWD  EQU  >8C00        VDP write data
VDPWA  EQU  >8C02        VDP set read/write address
WS1    EQU  >8300        Workspace memory in fast RAM
R0LB   EQU  WS1+1        Register zero low byte address

MSG1   TEXT ‘HELLO WORLD’
MSG1E
       EVEN

START  LIMI 0            Disable interrupts
       LWPI WS1          Load the workspace pointer to fast RAM

* Clear the screen
       CLR  R0           Start at top left corner of the screen
       LI   R1,>2000     Write a space (>20 hex is 32 decimal)
       LI   R2,768       Number of bytes to write

       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

CLS    MOVB R1,@VDPWD    Write byte to VDP RAM
       DEC  R2           Byte counter
       JNE  CLS          Check if done

* Write the text message to the screen
       LI   R0,395        Screen location to display message
       LI   R1,MSG1       Memory location of source data
       LI   R2,MSG1E-MSG1 Length of data to write

       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

DISP   MOVB *R1+,@VDPWD  Write a byte of the message to the VDP
       DEC  R2           Byte counter
       JNE  DISP         Check if done

       LIMI 2            Enable interrupts
INFLP  JMP INFLP         Infinite loop
       END

This is a complete example that can be entered via the Editor/Assembler, assembled and run.  When programming in any compiled language there are going to be parts of the program code that are not actually part of the language, but rather commands that the compiler will pay attention to and take certain actions.  These special commands are called “compiler directives” and assist us as programmers with making our programming task a little easier.  Some of the compiler directive are also carried over and used by the linking loader which is responsible for loading our program into memory and pointing the CPU to the start of our program.
Let’s cover each line of this program so you can get a good understanding of what is going on, what the assembler is doing for us, and what is actually assembly code.
       DEF  START

This is a compiler directive that specifies a label where our program will start executing.  The word “DEF” is the directive and, in this case, the word “START” is a label which we can choose.  This directive is required when writing relocatable code.  This is typically known as an EA3 program because you load and run your program from the Editor/Assembler menu option 3: Load and Run.  Since we will be using the EA3 option to load and run our program, we need to provide this directive.
VDPWD  EQU  >8C00        VDP write data
VDPWA  EQU  >8C02        VDP set read/write address

WS1    EQU  >8300        Workspace memory in fast RAM
R0LB   EQU  WS1+1        Register zero low byte address

These are also compiler directives called “equates” and function as a “search and replace” operation within our code during compilation.  We have provided a label and a value, so for example, any place in our program where the assembler sees “VDPWD” is will actually use the value >8C00 (using a greater than symbol before a number is how you write number in hexadecimal in your programs).  In the case of VDPWD, we are using that because as humans it is easier to say Video Data Processor Write Data address instead of knowing that the memory location >8C00 is where the VDP memory write address port is mapped in the 99/4A computer.  Any place in our code that we used VDPWD we could have written >8C00, but using the label makes our programs easier to read and more understandable.
The R0LB label is used when writing to the VDP and helps us save an instruction (which we will cover later).  The value for R0LB is another label, which is perfectly fine since the assembler knows what WS1 is, and the assembler can also do the math for us at compile time.  So, R0LB gets the value of >8300+1 or >8301.  Because we are going to load the CPU’s workspace register with WS1 (>8300), and register zero (R0) is located at the first two bytes in memory pointed to by the workspace register (addresses >8300 and >8301), the label is a convenient way to access R0’s low byte.
MSG1   TEXT ‘HELLO WORLD’
MSG1E
       EVEN

These are compiler directive also, however the TEXT directive actually causes memory to be reserved and initialized.  As the compiler is running it keeps track of certain things about our program, one of which is the current location within our program.  The assembler’s “location counter” starts at zero and is incremented any time the assembler processes instructions or data.  So far in our program the location counter has not moved because all the previous statements were compiler directive and did not generate any actual code that the 9900 CPU can execute.  The TEXT statement does not generate code either, but it does reserve memory for us, and in this case in the form bytes of data.  The characters between the open and close single quotes are converted to their ASCII numeric values and become part of our program’s binary code.  Those bytes are not actually executable instructions, but rather bytes of data that we will write to the screen and that will have meaning to a human user.
So, when the assembler sees the TEXT directive, it assigns the current location counter value (which in this case is zero) to our label, convert our text into byte values and stores those bytes in our program binary, and then adds 11 to the location counter (the length of our data).  The MSG1E (the ‘E’ stands for “end” and is completely arbitrary) label does not have any directive or instruction associated with it, but the assembler will still assign the current location counter value to the label which in this case is now 11.  We use MSG1E as a convenient way to get the length of our text message without specifically having to count the bytes ourselves or hard-code the length in our program.  Letting the assembler do this for us allows us to change the text message without having to update any other code.  You will see later we use MSG1E-MSG1, which becomes 11-0 since those were the values of the location counter when the assembler processed those directives.
The EVEN directive tells the assembler to make sure the current value of the location counter is on an even address.  Because our previous directives might have caused the program counter to be an odd value (which it the case here), and because program instructions cannot exist at odd addresses, we the EVEN directive to make sure our code will be generated properly at even addresses.  This directive is not as important in this case because the assembler will automatically force the location counter to an even address when it encounters a real program instruction.  However, if our TEXT directive had been followed by the DATA directive which reserves two-bytes of storage, it would be important to have the EVEN directive in between them.
START  LIMI 0            Disable interrupts

This is the first part of our program code that will actually generate machine instructions that the 9900 CPU can execute.  The label START is what we used in our DEF directive at the top of our program code and is the location where the linking loader will point the CPU to start executing our program.  Notice that our starting location is below where we reserved space for our message text.  So even though our message text will exist in our program binary, it will be skipped when our program executes.  This is a good thing because the CPU cannot tell the difference between what bytes in memory are data and what bytes are actual program instructions.  If the CPU gets off somehow, it will happily try to execute our data as actual program instructions and that usually results in a system crash.
The LIMI (load interrupt mask immediate) instruction will prevent the CPU from responding to external interrupts.  We are disabling them because when writing to the VDP we do not want our code to be interrupted.  Explaining the interrupt processes will be covered in another chapter, just keep in mind that when reading or writing to the VDP you will want interrupts disabled, so that is what this instruction does.  The word “immediate” in the instruction is referring to the zero, which is a discrete value that immediately follows the instruction in memory, as opposed to using a register value to load into the interrupt mask.  There are several “immediate” instructions and all of them require a discrete value as the only operand to the instruction.
       LWPI WS1          Load the workspace pointer to fast RAM

This instruction, Load Workspace Pointer Immediate, will take the operand value and load the CPU’s internal workspace pointer register with that value.  This is how we tell the CPU where in memory to store values for the 16 general purpose registers.  We used the assembler to equate WS1 with the value >8300, which is the first address in the 16-bit RAM in the 99/4A computer.  We use the 16-bit RAM because it is the only non wait-state memory in the machine and we want our register access to be as fast as possible.
       CLR  R0           Start at top left corner of the screen

This instruction will set the value or register zero to a value of zero.  We will use the value in R0 to indicate where on the screen we want to begin writing data.  In this case we are setting up to clear the screen so the starting address is character location 0, 0.  In assembly language most things start with zero, not one.  Without going into too much detail about the VDP right now, what we see on the screen is controlled by the Pattern Name Table (PNT) which has 1 byte for every character location on the screen.  Since the screen is 32x24 characters in Graphics Mode I (the default mode we are dealing with in this example), then there are a total of 768 bytes in the PNT with the first character in the table at VDP address zero (not to be confused with the 9900 memory addresses).  The PNT can be moved to a different starting location in the VDP RAM by setting certain VDP control registers, but for this example we do not need to do that and having the default PNT start at VDP address zero is convenient for us.  Thus, the first row of characters are VDP RAM addresses 0 to 31 (remember you have to count 0 as the first location).  PNT address 32 is the first character on the second row, PNT address 64 is the first character on the third row, and so on.
       LI   R1,>2000     Write a space (>20 hex is 32 decimal)

For clearing the screen we will be using R1 to hold the value to write to the PNT.  The Load Immediate instruction will load R1 with the immediate value >2000.  Remember that registers hold 16-bit values or two-bytes.  We use hexadecimal to load the register because it is a convenient way to see what is going into each byte of the register.  In this case >20 (32 in decimal, which is the ASCII code for the space character) is going into the high-byte and >00 is going into the low-byte.  We could have also written this as LI R1,8192 (>2000 = 8192 in decimal), but that is much more confusing as to what the value means.  We are also loading the >20 into the high-byte because the MOVB (move-byte) instructions that we use to move the data to the VDP RAM will always send only the high-byte or a register.  This is because the high-byte of a register is always at even memory address and the 9900 CPU is a 16-bit “word” oriented processor.  If we had written LI R1,>20 then the value loaded to the entire 16-bit register would be 32, which means the high-byte would be >00 and the low-byte would be >20, and that would not give us the value we need to write to the VDP’s PNT.
       LI   R2,768       Number of bytes to write

We are using R2 as a counter of how many bytes to write to the VDP.  Since we need a space character in every position of the PNT, and there are 32x24 characters on the screen, we need to write a total of 768 bytes.
       MOVB @R0LB,@VDPWA Send low byte of VDP RAM write address

This is the first instruction where we start communicating with the VDP.  Before we can write data to the VDP’s RAM, we first have to tell it the starting address where we want the data to be written.  To do this we have to write a 14-bit memory address to the VDP (the VDP can only address 16K of RAM, which can be represented with a 14-bit number).  The way in which the VDP is memory mapped to the 9900 CPU in the 99/4A computer is such that, by writing to memory location >8C02 a byte to be written to the VDP for setting up the VDP’s address register.  The VDP only has an 8-bit data bus, so we have to write the required 14-bit address as two bytes, the low-byte of the address first, then the high-byte.  When setting the address of the VDP’s address register, the top two BITS of the second address byte must be 0 and 1 respectively.
The Move Byte instruction here is performing a memory-to-memory move.  You know this because of the use of the “@” symbol which represents symbolic addressing, also known as “memory direct” addressing.  First the assembler will replace our labels with their equated values, so the instruction becomes: MOVB @>8301,@>8C02.  So this is saying, move the value of the byte found at memory address >8301 into the memory address >8C02.  The VDP happens to be mapped at >8C02 (the destination of the move byte instruction), and memory address >8301 is the low-byte of our workspace register zero (R0).
       ORI  R0,>4000     Set read/write bits 14 and 15 to write (01)

Now that we have sent the low-byte of the address to the VDP address register, we need to send the high-byte.  Recall that the VDP only needs a 14-bit address and that the top two bits of the second address byte must be 01.  So with the current instruction we are making sure the top two bits are 01 with the OR Immediate instruction.  This will do a logical OR operation with the value in R0 (our desired VDP memory address) and the immediate value >4000, which in binary is 0100 0000 0000 0000.  This is called a “mask” and the OR operation will leave the bottom 14 bits unaffected, but it will ensure the 15th bit is set to 1.  The 16th bit, like the bottom 14 bits will be unaffected, but should already be zero (as we need it) because the desired VDP address we initially loaded into R0 should never be more than a 14-bit address.
       MOVB R0,@VDPWA    Send high byte of VDP RAM write address

This will move the high-byte of R0, which contains the top part of the VDP address we are loading, into the memory address at VDPWA, which we know to be >8C02.  When the MOVB instruction is used with a register, the instruction will always operate on the high-byte of the register.  After this instruction the VDP now has its address register loaded with the address we indicated.  We can now start writing data to the VDP.
CLS    MOVB R1,@VDPWD    Write byte to VDP RAM

This instruction starts our loop for writing spaces to the VDP’s PNT.  This instruction moves the high-byte of R1 (which we set to >20) to the memory address at VDPWD which is equated to >8C00, and that is the address where the VDP’s data port is mapped in the 99/4A computer.
An interesting thing happens inside the VDP at this point.  Once you set up the VDP’s address register, the VDP will auto-increment its address register after each data byte written to it.  This is a very nice feature.  This means we only have to send the two address bytes to the VDP when we need to change the address where we want to write data.  Otherwise, each successive byte we write will go to the next location in the VDP’s RAM.  In this case we set up the VDP address in the address register to zero, so the first time we execute this instruction the >20 byte we send will go to the PNT address zero.  Then the VDP will increment the address register, which will then be 1.
       DEC  R2           Byte counter

Now we use the Decrement instruction to reduce the number of bytes we still have to write to the VDP.  Recall that we loaded R2 with 768 since that is how many screen locations there are in Graphics Mode I (32x24).  We use the DEC instruction because we get something for free, and free is good.  After the 9900 executes the decrement instruction, it compares the result of the decremented value to see if it is equal to zero.  If the value is zero, the 9900 will set the “equal” flag in the status register and we can check for that flag with several of the logical “jump” instructions.  This is a very powerful feature of the 9900 CPU and many of the instructions have their result values automatically compared to zero.  In this case it means that by counting down instead of counting up (from 0 to 768), we save having to code our own extra compare instruction within out loop.
       JNE  CLS          Check if done

The Jump if Not Equal instruction checks to see if the “equal” status flag in the status register is set (a value of 1), and if so it will jump to the designated offset, which in our case is the address associated with the CLS label, which is our MOVB instruction to write another >20 byte to the VDP.  The “equal” flag was affected by our previous DEC instruction when it compared the result of the decrement to zero.  If our counter value in R2 was not yet zero, then the equal flag would be reset (a value of 0) and the JNE instruction would be true (the result was not equal to zero) and the jump would be taken.  When the result of the DEC instruction is zero, then the “equal” flag will be set and the JNE will not take the jump and the CPU will continue execution with the next instruction after the JNE.  This will happen after 768 bytes have been written to the VDP, and at that point the screen will be cleared.
 

Make a Free Website with Yola.