Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

I wanted to know specifics behind these modes (before moving along) so I referenced the E/A Manual: 21.2 Graphics Mode, 21.3 Multicolor Mode, 21.4 Text Mode, 21.5 Bit Map Mode

 

You should have a look at the 9918A datasheet. It is more like a small book than a typical datasheet, and explains all the modes, how the tables are set-up and accessed, etc.

 

Q1: What on earth did they have in mind for Multicolor Mode?

IMO Multicolor Mode was added because it was a very simple modification to the VDP's state machine to implement. And having more "modes" is a marketing feature. I hate modes by the way, they are the "un-fun" part of making a VDP. Interestingly, the difference between GM1 and GM2, in terms of circuitry, is very small. I was shocked when I realized how little changes it took to add GM2.

 

Q2: Is it safe to assume Multicolor Mode is mostly avoided for game programming?

 

Probably. The biggest problem with MCM is that you cannot have any patterns, which means no text other than what you draw with 4x4 pixels, no tile patterns, etc.

 

Q3: Is Graphics Mode simply the same as what's available when coding in Extended BASIC?

 

Yes, BASIC and XB are both stuck in GM1. This is mostly due to the heavy use of VRAM for program and variable storage by the BASIC interpreter.

 

Q4: Is it ever advantageous to swap modes in the same program: Graphics/Text, Bit Map/Text, Graphics/Multicolor? Any examples in the wild?

 

Depends on the situation. Personally I do not find it useful, and the 9918A does not have a good way to trigger the point on the screen where you would reprogram the VDP registers to implement the mode change. Rasmus has demonstrated programs that do this, but the software spends a lot of time in polling loops watching the VDP status, which eats into the time your code has to run the game.

 

This is the main problem with most of the advanced effects on the 9918A, after you have implemented the effect you have little memory, VRAM, and/or CPU cycles left for anything else.

 

Q5: Is Bit Map mode simply Graphics mode with background/foreground color information available for the 8 individual character rows vs per entire character, and no Auto-Sprites?

 

From a visual point of view, yes. However, as previously explained, the way the VDP tables are laid out and used is different than GM1. Basically you get 2-color 8x1 "pixels". There have been a lot of impressive images and such made with GM2, but it requires a lot more VRAM and effort to use.

 

There is also an undocumented modification to GM2 typically called "half bitmap mode". In this variation of GM2 you have the same 256 tiles available like GM1, but you have the extra color information like GM2. This "mode" was used by a lot of games on other systems that used the 9918A. It is also easy to use like GM1.

 

​Q6: Are Auto-Sprites only a good deal for XB programs?

 

In a word, yes. The only reason for auto-sprites in XB is because XB is slow to manage sprites directly with any kind of decent response needed for fast-paced games. In assembly you have the speed to process and update the sprite positions yourself, and still have the desired smooth motion. This also allows you to have motions other than straight paths and such, and makes implementing a game loop, collision detection, etc. easier. After all, "auto sprites" are nothing more than an assembly routine that is executed in the console ISR.

  • Like 2
Link to comment
Share on other sites

I've written software that switches between text mode and bit-map (Graphics II) mode. Pascal starts and stops in text mode, and then I wanted to display diagrams while the program was running. So I went to bit-map for a while, then back again.

I've also gone from text to Graphics I and back to text, for similar reasons. I think it was because I needed sprites for something. I don't remember exactly.

  • Like 1
Link to comment
Share on other sites

Okay, so here are the VDP functions I promised. If you are reading this to learn assembly, then I *highly recommend* you type in this code instead of just copy and pasting. I know it sucks, but when you type each instruction you are forced to look at it in detail and it helps drive home what is going on. Also, you get more familiar with actually writing code, which is what you are trying to do anyway. Personally I always learn something or resolve a problem by typing in the examples.

I put this code at the bottom of my programs, just above the character definitions I posed before is a good place.


 

This type-it-in approach works well for me too. This is my homework assignment for the evening.

 

This reminds me of learning by typing code in from the pages of Creative Computing in the 80s.

Link to comment
Share on other sites

Okay, so here are the VDP functions I promised. If you are reading this to learn assembly, then I *highly recommend* you type in this code instead of just copy and pasting. I know it sucks, but when you type each instruction you are forced to look at it in detail and it helps drive home what is going on. Also, you get more familiar with actually writing code, which is what you are trying to do anyway. Personally I always learn something or resolve a problem by typing in the examples.

 

Matthew

 

 

Matthew,

 

I was able to type in the VDP routines and get them to Assemble along with some code:.

 

DEF VDPRTN * VDP Functions from matthew180 @ AtariAge

* from AtariAge post 24 May 2010 - 14 May 2010

REF VSMW

* REF We're going to "roll our own" = better, faster, smaller code; no E/A cart required

* The floating point routines in the E/A & XB carts are horribly slow. Primarily due to setting the WP

* to the lower 8K of the expansion RAM which is 8-bit; every access to registers will cause 8 wait

* states (since 16-bit registers always access 2 bytes) <=== Best reason to avoid

 

* VDP Memory Map - VDP RAM is accessed by the following four decoded address lines or "ports":

VDPRD EQU >8800 * VDP Read Data

VDPSTA EQU >8802 * VDP Read Status

VDPWD EQU >8C00 * VDP Write Data

VDPWA EQU >8C02 * VDP Read/Write Address

 

* EQUates are not assembly opcodes, they're compiler directives used to make labels for constant memory values

WRKSP EQU >8300 * Workspace in beginning area of 16-bit fast "CPU RAM"

RoLB EQU WRKSP+1 * Register-0 LSB

 

VDPRTN LIMI 0 * disable interrupt because the ISRs use the heck out of CPU RAM

* which would otherwise trash out game: destroy Registers and Variables

 

LWPI WRKSP * CPU RAM

* NOTE: All "immediate" instructions for TMS9900 require a numeric value

* and not a memory or regiser operands.

***********************************************************************************************************************

* PROGRAMMING CONVENTIONS *

***********************************************************************************************************************

* Register Use

* -------- -----------------------------------------------------------------------------------------

* R0-R2 always used for VDP interaction

* R3-R6 general purpose

* R7 used when calling the random number routine (think tombstone city code)

* R8-R9 just extra registers

* R10 a pseudo stack number for calling a few levels of sub routines

* R11 BL uses this register to save the current PC value so a subroutine may return with B *R11

* R12 ued by CRU instructions and needed for keyboard and joystick checking

* R13-R15 extra unless you want to use BLWP which uses them

*

***********************************************************************************************************************

 

******************************

* Code below is used to test and play around with the "home brew" VDP Routines: VSBW, VSMW, VMBW, VSBR, VMBR, VWTR

*

 

MAINLP LI R0,>0000

LI R1,>2300 * fills the screen with pound sign manaully, one position at a time

LI R2,768

MOVB @RoLB,@VDPWA

ORI R0,>4000

MOVB R0,@VDPWA

 

POUND MOVB R1,@VDPWD

DEC R2

JNE POUND

 

LI R2,>FFFF * delay 1

DEL1 DEC R2

JNE DEL1

 

 

LI R0,>0000

LI R1,>2000 * clears the screen using VSMW, inserting 768 spaces

LI R2,768

CLS BL @VSMW * calls VSMW as a common workspace subroutine

 

LI R2,>FFFF * delay 2

DEL2 DEC R2

JNE DEL2

 

JMP MAINLP

 

END

 

 

 

***********************************************************************************************************************

* SUBROUTINE CODE BEGINS *

***********************************************************************************************************************

 

***********************************************************************************************************************

* VDP SINGLE BYTE W R I T E

*

* follows EA Register conventions: R0 = Write address in VDP RAM

* R1 = data in MSB of R1 will be sent to VDP RAM

*

* Modifies: R0 can be restored with: ANDI R0,>3FFF

*

* Avoids: BLWP ==> no 32bytes CPU RAM wasted, no slow 8-bit RAM used, avoids BLWP 26-clock cycles

*

VSBW MOVB @RoLB, @VDPWA * send the LSB of VDP RAM address to VDP address reg, 1st sent is always LSB

ORI R0, >4000 * sets bits 14/15 to = 01 which signals a WRITE once sent to VDP address reg

* ( assumes 00 is ALWAYS in bits 14/15 of R0 )

MOVB R0,@VDPWA * send high byte of VDP RAM address and write signal bits to VDP address reg

MOVB R1,@VDPWD * sends a byte of data to the VDP RAM

B *R11 * return

*//VSBW

 

***********************************************************************************************************************

* VDP SINGLE BYTE M U L T I P L E -- W R I T E -- same BYTE of data to multiple concurrent locations in VDP RAM

*

* follows EA conventions: R0 = Write address in VDP RAM

* R1 = MSB of R1 sent to VDP RAM

*

* R0 Starting write address in VDP RAM

* R1 data in MSB of R1 will be sent to VDP RAM

* R2 # of times to write MSB of R1 to VDP RAM

*

* Modifies: R0 can be restored with: ANDI R0,>3FFF

*

VSMW MOVB @RoLB,@VDPWA * send LSB of VDP RAM address to VDP address reg, 1st sent is always LSB

ORI R0,>4000 * sets bits 14/15 to = 01 which signals a WRITE once sent to VDP address reg

* ( assumes 00 is ALWAYS in bits 14/15 of R0 )

MOVB R0,@VDPWA * send MSB of VDP RAM address and write signal bits to VDP address reg

VSMWLP MOVB R1,@VDPWD * sends a byte of data to the VDP RAM

DEC R2 * decreases the loop counter

JNE VSMWLP

B *R11 * return

*// VSMW

 

 

***********************************************************************************************************************

* VDP Multiple Byte W R I T E ( writing BLOCKS OF DATA from address in CPU RAM to address in VDP RAM )

*

* R0 starting Write To address in VDP RAM

* R1 starting data location address in CPU RAM

* R2 number of bytes we're sending from CPU RAM to VDP RAM

*

* Modifies: R0, restore R0 with ANDI R0,>3FFF

*

VMBW MOVB @R0LB,@VDPWA *send LSB of VDP RAM write address to VDP address reg, 1st sent is always LSB

ORI R0,>4000 *sets read/write bits 14/15 to (01) to signal WRITE

* ( assumes 00 is ALWAYS in bits 14/15 of R0 )

MOVB R0,@VDPWA *send MSB of VDP RAM write address and write signal bits to VDP address reg

VMBWLP MOVB @R1+,@VDPWD *write byte from CPU RAM to VDP RAM

DEC R2 * decrease loop counter

JNE VMBWLP

B *R11 * return

*//VMBW

 

 

***********************************************************************************************************************

* VDP SINGLE Byte R E A D ( reads VDP RAM data byte into Register-1's MSB )

*

* R0 contains the address in VDP RAM from where the data will be read

* R1 MSB of R1 will receive the data from the hardwired VDP data bus

*

VSBR MOVB @RoLB,@VDPWA * send LSB of VDP RAM read address to VDP address register, 1st sent always LSB

MOVB R0,@VDPWA * send MSB of VDP RAM read address to VDP address register

* ( assumes read/write bits 14/15 are already 00 which signals a READ

MOVB @VDPRD,R1 * read VDP RAM into R1

B *R11 * return

*//VSBR

 

 

***********************************************************************************************************************

* VDP M U L T I P L E Byte R E A D ( reads VDP RAM data bytes into CPU RAM identified by Register-1's MSB )

*

* R0 contains the starting read address in VDP RAM

* R1 contains the starting write address in CPU RAM

* R2 number of bytes to read from VDP RAM to CPU RAM

*

VMBR MOVB @RoLB,@VDPWA * send LSB of VDP RAM read address to VDP address register, 1st sent always LSB

MOVB R0,@VDPWA * send MSB of VDP RAM read address to VDP address register

* ( assumes read/write bits 14/15 are already 00 which signals a READ

VMBRLP MOVB @VDPRD,*R1+ * read byte from VDP RAM to CPU RAM address in R1, then inc R1

DEC R2 * decrease loop counter

JNE VMBRLP

B *R11 * return

*//VMBR

 

 

***********************************************************************************************************************

* VDP Write to a VDP REGISTER (from Workspace Register Zero)

*

* R0 MSB contains the VDP Register to WRITE to

* R0 LSB contains the value to write to the VDP Register

*

VWTR MOVB @RoLB,@VDPWA * send LSB (data value) to be written to the LSB of VDP address register

ORI R0,>8000 * signal VDP Register Write operation bits 14/15 = (10)

* * assumes bits 14/15 are ALWAYS (00)

MOVB R0,@VDPWA * send VDP register number and WRITE signal to MSB of VDP r/w address

B *R11

*//VWTR

 

 

 

Yet was unable to get the main program to execute the BL to @VSMW?

 

​Made that line a comment and everything ran (without clearing the screen of course).

 

asm994a assembled everything error free yet Classic99 gave me " ERROR CODE 0D" when I attempted to run it?

 

​- James

Link to comment
Share on other sites

Why are you REFing the VSMW? You implement it right here.

Thanks for the fast feedback. I was getting "Undefined symbol" from the Assembler when I referenced the subroutine VSMW, so I added the REF and that error went away.

 

Obviously, I've done something wrong here. This thread is all about learning so basically I don't quite understand what that REF did?

 

Did I import a routine from the EA ROM by the same name?

 

I'll have to research this all in the AM. Thanks.

 

-j

Link to comment
Share on other sites

RoLB and ROLB are the same, but R0LB is not. It should be R0LB in this case.

 

And yes, END marks the physical end of what should be assembled, not the logical end of the main program.

 

When you assemble a program, the assembler is reading your source and makes an object code out of it. Instructions like SWPB R4 can be assembled immediately, as they are completely defined. But an instruction like B @KALLE can't, if you don't know what the value of KALLE is. Each time the assembler program encounters such an instruction, it will remember that there's a referenced to KALLE that's unresolved. When it later finds KALLE in the program, it can fill in the value of KALLE in the instruction, and the problem is solved.

While the assembler is running, it will build a symbol table with all names defined in the program, and eventually it will have a value associated with all of them. If not, you'll get an error at the end of the process, since then there are unresolved references which the assembler can't resolve.

 

You can make code absolute or relocatable. When it's absolute, it's always possible to fully resolve all addresses during assembly. You know for example that the code should load at >A000, so you know that an address, like KALLE, that's >10 bytes down the code must be >A010. The CPU can only execute absolute code.

But when the code is relocatable, you don't know where it will load. It could be at >A000, but it could also be at >2BC4. Or any other address. The only thing you know about KALLE then is that it's +>10 bytes down from the origin of the code.

This is where tagged object code and the linking loader comes in. The assembler produces tagged code, where each word is tagged with information describing the content. An instruction like SWPB R4 will be tagged with the load as it is tag. No changes needed. But the address KALLE will be tagged by the add +>10 to the start address of the code tag. Thus the assembler will resolve the address relative the code segment start, and then the loader will finally resolve the address by adding the code segment loading address. When that's completed, the code in memory is now absolute and can be executed.

 

But the loader supplied with the TI 99/4A is more clever than that. It's not only a loader capable of loading relocatable code, but it's a linking loader. That implies that you can load code that contains addresses that are completely unknown at assembly time. If you have a reference to KALLE, but nowhere in your code define KALLE, then KALLE is unresolved. But by including the directive REF KALLE, you can tell the assembler that you are aware of that KALLE is needed in the program, but KALLE is in another program, so you'll not know until at load time what KALLE is. The assembler will then tag references to KALLE by the resolve at load time tag. This is where the linking loader comes in. When it finds a reference to KALLE, it will look at a table called the REF/DEF table to find out the address of KALLE, and put that address into memory where the reference to KALLE is. Now the code has become absolute and can be executed.

 

So where does the definition of KALLE come from? Well, in this example it's assumed that:

  1. You have written another program, that contains the definition of KALLE.
  2. That piece of code also contains the directive DEF KALLE, to indicate that the definition of KALLE is external, and thus made available to other programs.
  3. You have loaded this code, with DEF KALLE inside, before you load the code that contains REF KALLE.

When we look at the routines like VSBW and similar, it's the E/A system that pre-loads, and defines, some routines for you. They include VSBW. In the example above, you got away with REF VSMW ​at assembly time, because it simply tells the assembler that it's up to the loader to fix the VSMW reference. But at load time, it would have exploded, since the label VSMW wasn't actually externally DEFined by any other program.

 

This whole concept is a powerful one, since it allows you to build libraries of frequently used functions, and include external definitions in these libraries. They may then be externally referenced from other programs, so that you can load the same library as one code file, and use it from different other code files. This is exactly what the E/A system provides for you.

Since the tagged object code loader can load relocatable code at any address it finds convenient, it doesn't matter how big the library is. Your program will always load after it, regardless of where that's in memory. This was a capability inherited from the system used in the TI 990 minicomputers, and was more powerful than what was available in several contemporary home computers.

  • Like 4
Link to comment
Share on other sites

RoLB and ROLB are the same, but R0LB is not. It should be R0LB in this case.

 

And yes, END marks the physical end of what should be assembled, not the logical end of the main program.

 

 

Yep! I must have had R<zero>LB in there. Good to know RoLB = ROLB so no case sensitivity. I was using the "END" as I had with BASIC I suppose. So it's an Assembler Directive, not an OpCode.

 

 

 

You can make code absolute or relocatable. When it's absolute, it's always possible to fully resolve all addresses during assembly. You know for example that the code should load at >A000, so you know that an address, like KALLE, that's >10 bytes down the code must be >A010. The CPU can only execute absolute code.

 

Is it safe to assume I'll be writing strictly relocatable code vs absolute code?

 

Thus the assembler will resolve the address relative the code segment start, and then the loader will finally resolve the address by adding the code segment loading address. When that's completed, the code in memory is now absolute and can be executed.

 

The way you phrased this makes it easy for me to understand. I didn't get that earlier in my reading.

 

 

But the loader supplied with the TI 99/4A is more clever than that. It's not only a loader capable of loading relocatable code, but it's a linking loader. That implies that you can load code that contains addresses that are completely unknown at assembly time.

 

Same comment as above.

 

That piece of code also contains the directive DEF KALLE, to indicate that the definition of KALLE is external, and thus made available to other programs.

 

I didn't realize this was happening with DEF making it external/global. This is quite powerful and a big potential time saver.

 

Wondering if this how SPECTRA by Retroclouds works? A collection of arcade game enabling routines imported by REF's at the beginning of my code?

 

  1. You have loaded this code, with DEF KALLE inside, before you load the code that contains REF KALLE.

 

My Assembler workflow includes using Classic99 with the E/A Cart, Option 3 Load and Run.

 

With multiple source code files I'd have to load.... say... the VDP Routine file before the Main Program file. I'd need DEFs in the VDP Routine file and associated REFs in the Main Program file. I'd have to remember to load the VDP Routine file first.

 

How do most people automate this?

 

I anticipate issues with several or more support (DEF) packages.

 

 

In the example above, you got away with REF VSMW ​at assembly time, because it simply tells the assembler that it's up to the loader to fix the VSMW reference. But at load time, it would have exploded, since the label VSMW wasn't actually externally DEFined by any other program.

 

This really clears up a lot regarding REF v DEF.

 

 

This whole concept is a powerful one, since it allows you to build libraries of frequently used functions, and include external definitions in these libraries. They may then be externally referenced from other programs, so that you can load the same library as one code file, and use it from different other code files. This is exactly what the E/A system provides for you.

 

Also seems it'd become tedious if you had more than a few libraries to load.

 

Since the tagged object code loader can load relocatable code at any address it finds convenient, it doesn't matter how big the library is. Your program will always load after it, regardless of where that's in memory. This was a capability inherited from the system used in the TI 990 minicomputers, and was more powerful than what was available in several contemporary home computers.

 

Always appreciate a little History with my daily computer lessons! THANK YOU! Great reply!

Link to comment
Share on other sites

And when you finally create a program file from your application together with its modules so that you can load it by option 5, this corresponds to "static linking" in PC-like environments.

 

On the Geneve, you have e.g. the TASM assembler, and the LDR loader, which takes the part of the option 3 loader and the SAVE utility that statically links the files.

Link to comment
Share on other sites

I just happened to see this thread. I'm not believing there is a thread with 18 pages for assembly on the 99/4A. I mean I just don't get it. The one I have one that came pre-assembled. I didn't have to put much of anything together.

 

I'll help out. Plug in the power, then plug in the AV. plug in any expansion modules. Done, assembled. Hope that helps out.

 

 

Yes that was a joke I was bored.

  • Like 1
Link to comment
Share on other sites

Note: you should put code inside the [ code ] and [ / code ] tags (without the spaces). You can also use the [ spoiler ] and [ / spoiler ] tags to collapse long code listings and make it easier to read. Net the code tags inside the spoiler tags.

...

Is it safe to assume I'll be writing strictly relocatable code vs absolute code?
...


No, not if you plan to write code to run from a cartridge ROM, support routines for BASIC/XB, or if you ever start writing for the F18A. When you are getting started it is usually easier to write relocatable code and let the loader decide where to put things, but you will certainly need to understand when and how to use both.

...
I didn't realize this was happening with DEF making it external/global. This is quite powerful and a big potential time saver.
...


REF and DEF are strictly assembler directives. Every label in your code is just a placeholder for whatever address the assembler ultimately assigns to that location, which depends on the code and data that make up your program. Labels are nice because they allow us to not have to worry about memory addresses, and we can add code above and below the labels and let the assembler work out the final address.

Think of memory addresses like line numbers in BASIC, and how much of a pain it is when you have to specify a line number in your "THEN", "GOTO", "GOSUB", etc. statements. Then as your program grows and changes you have to go back and adjust all those line numbers in the statements. Having labels avoids all the fuss and lets the assembler work out the final memory addresses for all the labels. Using labels in assembly is similar to what TidBit does for BASIC/XB.

 

When you assemble code, DEF tells the assembler to make the specified labels "defined" in the REF/DEF table. REF tells the assembler that the addresses for those labels will be provided later (by the linker/loader).

 

Using those directives you can expose functions in your code, and use functions from other compiled code.

 

When you "load and run" your object file, the linker/loader (typically just called the "loader") will look at the object file being loaded and check for any "defined" labels in them. It will take the addresses associated with those labels and put them in the REF/DEF table. Then it looks at the "references" from the object file, looks in the REF/DEF table to find addresses for those references, and uses those addresses to "fix up" the object code as it is being loaded.

 

This "fixing up" of addresses at load-time is what makes your code relocatable. This is also why, right now, you have to use the "load and run" option, because you are generating an object file that must have all label references in your code fixed-up.

 

This is also why you have to "define" your starting point in your assembly code. This one label and its address will be added to the REF/DEF table, which is what lets you type that name in the "run" part of load-and-run. This is also why you can specify multiple object files during the "load" part, because you may have multiple object files to load depending on your code.

 

So basically, REFs tell the assembler that those labels will be resolved later at link/load time. DEFs tell the assembler to make those labels and their address available to the linker/loader to be included in the REF/DEF table at link/load time.

 

The REF you had in the code you posted above is not necessary because VSMW is just a label in your own code. You are not trying to reference an external label, so you don't need any REF directives. Your code is completely self contained. I assume the error you probably received was something like an external reference error during loading. There was no VSMW routine in the REF/DEF table.

...
Wondering if this how SPECTRA by Retroclouds works? A collection of arcade game enabling routines imported by REF's at the beginning of my code?
...

 

Not sure, I have not looked at the lib, but I would suspect yes. You also need the object code of the library available at link/load time, and it would probably need to be loaded first.

 

...
With multiple source code files I'd have to load.... say... the VDP Routine file before the Main Program file. I'd need DEFs in the VDP Routine file and associated REFs in the Main Program file. I'd have to remember to load the VDP Routine file first.
...

 

During assembly you have multiple source files that are all assembled into one object file. In that case you do not need REFs and DEFs for those routines, the assembler will have all the labels available. If you are going to treat parts of your code like a library (i.e. write and assemble them separately), then in that case you would need to set up the REFs and DEFs, and have the object files available at load time (and load them in the correct order).

 

...
Also seems it'd become tedious if you had more than a few libraries to load.
...

 

It would be. However, keep in mind that this is a limited system and the number of libraries you use will typically be small, and usually zero. This is not like programming on a modern system with tons of required libs and such. Most of your assembly code will be your own, and you may have a few libs that you use, if any. Under emulation you can also automate the keystrokes of loading the libs and running your code.

Edited by matthew180
  • Like 2
Link to comment
Share on other sites

No, not if you plan to write code to run from a cartridge ROM, support routines for BASIC/XB, or if you ever start writing for the F18A. When you are getting started it is usually easier to write relocatable code and let the loader decide where to put things, but you will certainly need to understand when and how to use both.

For cartridge ROM yes, but both BASIC (with E/A module) and Extended BASIC support loading their assembly routines as relocatable code. That implies that you don't need to think about where to load the code. With relocatable code, you could have a library A, and two software packages B and C, which you want to make available to BASIC. Both B and C requires A to be loaded, but neither B nor C requires the other one to be present to work.

This then implies that you can let the BASIC program load A, then B and finally C. Or load A, then C and finally B. Or even make the program load none, and only load A and B if B is needed, or load A and C if C is needed, or augment A and C with B if that later is needed.

This automates the loading of the different assembly routines.

 

Using absolute code as support to BASIC/Extended BASIC only makes sense to speed up loading. But it doesn't make life easier for the programmer.

 

For automation, as I hinted above, BASIC can handle loading the necessary code files. When using the E/A module, it's also possible to include REF LOADER in your program, then set up a PAB for file access and then BLWP @LOADER to let an assembly program use the linking loader to load more assembly routines.

Link to comment
Share on other sites

This is the highly anticipated (well, for Owen anyway. icon_winking.gif ) game loop post!

*********************************************************************** Plot a random character*PLOT*      Only draw on the VSYNC       C    @VSYNC,@NUM01     * If VSYNC is not active, return       JEQ  PLOT01       B    *R11PLOT01       MOV  R11,*R10+         * Push return address onto the stack*      Get a random screen location       BL   @RANDNO           * Get a random number (in R5)       LI   R3,768       CLR  R4                * Dividend will be R4,R5       DIV  R3,R4             * Make a number between 0 and 767       MOV  R5,R0             * Move to R0 for the VDP routine       AI   R0,NAMETB         * Adjust to the name table base*      Get a random character 40, 48, 56, 64       BL   @RANDNO           * Get a random number (in R5)       SRL  R5,14             * Make a number between 0 and 3       SLA  R5,3              * Multiply by 8 (number is now 0, 8, 16, 24)       A    @CHR040,R5        * Add to the base character       MOV  R5,R1       SWPB R1                * Remember, the MSB goes to the VDP!       BL   @VSBW       DECT R10               * Pop return address off the stack       MOV  *R10,R11       B    *R11*// PLOT

Hey Matthew,

 

I was able to easily understand most of this post up to the point where your code started plotting random characters on the screen. The DIV OpCode is where I started to stumble as I've never really used it before. I went to p.88 of the E/A manual as a reference.

 

To help in my understanding of the logic I added comments as notes. A way to further clarify what exactly is going on between registers during the Divide, etc.

 

Here's what I came up with in order for it all to be clear in my mind:

*********************************************************************** Plot a random character*PLOT*      Only draw on the VSYNC       C    @VSYNC,@NUM01     * If VSYNC is not active, return       JEQ  PLOT01       B    *R11PLOT01       MOV  R11,*R10+         * Push return address onto the stack*      Get a random screen location       BL   @RANDNO           * Get a random number (in R5)... between 0 and 65,535 (2^16 -1)       LI   R3,768            * by cleverly using 768 as a divisor we assure any DIV remainder is ALWAYS 0-767       CLR  R4                * Dividend will be 32-bit R4&R5 (two words used by DIV) which is really just*                               a 16-bit dividend (R5) with leading zeros in R4 to satisfy the TI DIV OpCode       DIV  R3,R4             * divides destination operand (a consecutive 2-word workspace, R4&R5) by 768*                               Makes a number between 0 and 767 (see E/A manual p.88) in R5*                               - the resulting integer quotient (R4) is ignored here, does not matter*                               - the resulting integer remainder (R5) is ALWAYS between 0 and 767*       MOV  R5,R0             * Move to R0 for the VDP routine       AI   R0,NAMETB         * Adjust to the name table base*      Get a random character 40, 48, 56, 64       BL   @RANDNO           * Get a random number (in R5), again 0-65,535       SRL  R5,14             * Make a number between 0 and 3 using Shift Right Logical*                               Shifts bits in R5 right 14 times and fills left with zeros, leaving 2 LS-bits       SLA  R5,3              * Multiply by 8 (number is now 0, 8, 16, 24) using Shift Left Arithmetic*                               Shifts bits 1&0 left to positions 4&3, and fills in the right bits (2,1,0) with zeros       A    @CHR040,R5        * Add to the base character       MOV  R5,R1       SWPB R1                * Remember, the MSB goes to the VDP!       BL   @VSBW       DECT R10               * Pop return address off the stack       MOV  *R10,R11       B    *R11*// PLOT
Does this all seem correct to you? I want to make sure my understanding of the situation is correct.

 

I'm finding this thread incredibly helpful. Forgive my snails pace as I pick my way through the lessons. To me it seems to be moving along quite well yet based on the dates of your original posts I'm moving along a bit slower than Owen and the guys from 2010. Thanks again!

 

-James

Edited by Airshack
Link to comment
Share on other sites

* ...
       CLR  R4                * Dividend will be 32-bit R4&R5 (two words used by DIV) which is really just
*                               a 16-bit dividend (R5) with leading zeros in R4 to satisfy the TI DIV OpCode
       DIV  R3,R4             * divides destination operand (a consecutive 2-word workspace, R4&R5) by 768
*                               Makes a number between 0 and 767 (see E/A manual p.88) in R5
*                               - the resulting integer quotient (R4) is ignored here, does not matter
*                               - the resulting integer remainder (R5) is ALWAYS between 0 and 767
* ...

 

You may understand this perfectly well, but the above code snippet might indicate otherwise. Just to be sure, DIV does operate on a 32-bit dividend in R4—R5. The only caveat is that R4 (MSW) must be smaller than R3, the divisor. Otherwise, the overflow bit will be set and R4—R5 will be untouched. R4 is not ignored. It is the MSW of the 32-bit dividend. It so happens as you indicated, that the dividend happens to fit in the 16 bits of the LSW.

 

...lee

  • Like 1
Link to comment
Share on other sites

 

You may understand this perfectly well, but the above code snippet might indicate otherwise. Just to be sure, DIV does operate on a 32-bit dividend in R4—R5. The only caveat is that R4 (MSW) must be smaller than R3, the divisor. Otherwise, the overflow bit will be set and R4—R5 will be untouched. R4 is not ignored. It is the MSW of the 32-bit dividend. It so happens as you indicated, that the dividend happens to fit in the 16 bits of the LSW.

 

...lee

Thanks For the rapid feedback Lee! I read that about the source operand having to be larger than the first word of the destination operand, for "normal division to occur." Otherwise the resulting quotient won't fit inside a 16-bit word.

 

What I meant by "ignored" (R4) is this specific code does nothing with the number (integer quotient) in R4 after the DIV executes. It's only concerned with the remainder.

 

Seem right you? Otherwise, I will move on with the assumption that it's all understood.

 

The big takeaway here was learning the peculiarity of the DIV OpCode, which is only peculiar to me because there's nothing similar going on with BASIC regarding which end of the equation needs to be smaller.

Edited by Airshack
Link to comment
Share on other sites

This has nothing to do Airshack's questions, but I'm just wondering if there is any use for the 'unsigned conditions' JH, JL, JHE, etc. beyond use with compare? For instance, after a subtraction the result is compared to zero so JH and JHE will always be true.

Link to comment
Share on other sites

This has nothing to do Airshack's questions, but I'm just wondering if there is any use for the 'unsigned conditions' JH, JL, JHE, etc. beyond use with compare? For instance, after a subtraction the result is compared to zero so JH and JHE will always be true.

 

Re JH and JHE after subtraction, the jump path for JHE is the only one always taken, i.e., one, but not both, of the L and EQ flags will be set in comparisons to 0. Conversely, the jump path for JL will never be taken and that for JLE, only when the result is 0.

 

...lee

Link to comment
Share on other sites

Thanks For the rapid feedback Lee! I read that about the source operand having to be larger than the first word of the destination operand, for "normal division to occur." Otherwise the resulting quotient won't fit inside a 16-bit word.

 

What I meant by "ignored" (R4) is this specific code does nothing with the number (integer quotient) in R4 after the DIV executes. It's only concerned with the remainder.

 

Seem right to you?

 

Certainly.

 

...

The big takeaway here was learning the peculiarity of the DIV OpCode, which is only peculiar to me because there's nothing similar going on with BASIC regarding which end of the equation needs to be smaller.

 

Hm-m-m ... I do not see why you should consider DIV’s behavior peculiar. You actually do not need to consider its actions anything other than dividing a 32-bit dividend by a 16-bit divisor that produces a 16-bit quotient and a 16-bit remainder—unless there is an error due to overflow. It just happens that the TMS9900 checks for overflow before attempting the division by checking that the source word is larger than the destination MSW. It is just the easiest way to insure that the quotient fits in 16 bits.

 

TI Basic is really no different regarding overflow, except that the operation is floating point. Overflow will produce a “* WARNING: NUMBER TOO BIG” message. The only functional difference is that DIV will leave the source and destination alone, while TI Basic will return the largest possible floating point number (9.9999999999999E+127). Of course, TI Basic will only print “9.99999E+**”. “**” is printed because the perfectly legitimate exponent, 127, is one character wider than TI Basic allows for printout! I fixed that in fbForth.

 

...lee

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...