Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

20 minutes ago, matthew180 said:

What you don't get from the datasheet is the interplay between your code, the console ISR, and the VDP.  This is why I really like to turn off the console ISR, and recommend for people learning to do the same.

This is TI's recommendation too, for very similar reasons. Most applications have a main loop that cycles more than quickly enough for the interrupts to maintain 60hz.

 

  • Like 5
Link to comment
Share on other sites

I'm not a game programmer but I certainly have learned from this topic and others.  It wasn't until this topic started (and maybe a related topic from a game-writing contest?) that it finally sunk in and I realized it was not necessary to use manually-tuned timing loops for "everything" and that I could use all that wasted time for other tasks.  Alas, delay loops were a commonly used approach in 'learning assembly' examples and in the program code that was shared back in the day. And I don't know if it was just me, but most of the interrupt code I encountered was for displaying a "clock" on the screen, which didn't do much for the imagination.  :) Anyway, I think that the past 10-15 years have been very good for the community:  there has been a wealth of technique, information, and spirited conversation to push the envelope.  Neat stuff all around. 

 

 

  • Like 10
Link to comment
Share on other sites

Note that rather than using BLWP to call VWTR, VSBW, etc., you can roll your own with BL but return with RTWP.  This requires you to understand that a BL stores return PC (program counter) address in current R11 which needs to be moved to R14.  Your VWTR routine could look something like this:

 

VWTR   STWP R13  store the workspace pointer in R13 for RTWP

            MOV  R11,R14 store the return address in R14 for RTWP

... do the Video Write To Register

... store something in R15 if you want to update status register, but recall that the 9900 does not implement all bits

... only ST0-6,12-15 are implemented on TMS9900, but ALL bits are available in R15 upon return

... status register will always have ST7-11 equal 0 on TMS9900, but TMS9995 and other family members will store the bits in status register

            RTWP exit the routine

 

Why would you do this?  The vector for a BLWP takes two words of memory.  The STWP R13 and MOV R11,R14 take two words of memory.  No memory savings.

 

However, a normal RTWP called with BLWP will restore the status to the state it was prior to BLWP unless you fiddle with R15.  On the TMS9900, ST7-11 will always be 0 upon the BLWP call.  Want to pass information within those bits, use BL.

 

Jeff White

 

p.s.: Note, BL is not a context switch.  The same workspace is used for the routine as the caller.  BL uses "global" registers while BLWP uses "local" registers.  There are tradeoffs of both techniques, and I would evaluate both for what is necessary to accomplish, be it speed, portability, or other.

 

p.p.s.: After reading this, I should clarify that you can do VWTR, VSBW, etc. all with a standard BL returning with RT (B *R11).  The workspace is share with the calling routine, just as my abomination of calling with BL and returning with RTWP.

Edited by Jeff White
  • Like 4
  • Confused 1
Link to comment
Share on other sites

2 hours ago, Jeff White said:

Note that rather than using BLWP to call VWTR, VSBW, etc., you can roll your own with BL but return with RTWP.  This requires you to understand that a BL stores return PC (program counter) address in current R11 which needs to be moved to R14.  Your VWTR routine could look something like this:

 

VWTR   STWP R13  store the workspace pointer in R13 for RTWP

            MOV  R11,R14 store the return address in R14 for RTWP

... do the Video Write To Register

... store something in R15 if you want to update status register, but recall that the 9900 does not implement all bits

... only ST0-6,12-15 are implemented on TMS9900, but ALL bits are available in R15 upon return

... status register will always have ST7-11 equal 0 on TMS9900, but TMS9995 and other family members will store the bits in status register

            RTWP exit the routine

 

Why would you do this?  The vector for a BLWP takes two words of memory.  The STWP R13 and MOV R11,R14 take two words of memory.  No memory savings.

 

However, a normal RTWP called with BLWP will restore the status to the state it was prior to BLWP unless you fiddle with R15.  On the TMS9900, ST7-11 will always be 0 upon the BLWP call.  Want to pass information within those bits, use BL.

 

Jeff White

 

p.s.: Note, BL is not a context switch.  The same workspace is used for the routine as the caller.  BL uses "global" registers while BLWP uses "local" registers.  There are tradeoffs of both techniques, and I would evaluate both for what is necessary to accomplish, be it speed, portability, or other.

 

p.p.s.: After reading this, I should clarify that you can do VWTR, VSBW, etc. all with a standard BL returning with RT (B *R11).  The workspace is share with the calling routine, just as my abomination of calling with BL and returning with RTWP.

That's clever.  Another thing I have to try. 

 

I scratched my head for awhile trying to figure out a minimal way to context switch.

I didn't like taking up memory to make vectors for each task when they would just be copied into registers which is also memory. ;) 

My solution was to also play a game with RTWP for round-robin mutli-tasking by presetting R13,R14,R15 in every task so they all point back to each other in a circle.

This means the vectors live in registers not data memory. 

Then to context switch, I just run RTWP. :)  

I think that with later CPUs in the TI universe they added an instruction that did something like this but I can't remember where I saw it or how it worked.

 

 

  • Like 2
Link to comment
Share on other sites

5 hours ago, TheBF said:

That's clever.  Another thing I have to try. 

 

I scratched my head for awhile trying to figure out a minimal way to context switch.

I didn't like taking up memory to make vectors for each task when they would just be copied into registers which is also memory. ;) 

My solution was to also play a game with RTWP for round-robin mutli-tasking by presetting R13,R14,R15 in every task so they all point back to each other in a circle.

This means the vectors live in registers not data memory. 

Then to context switch, I just run RTWP. :)  

I think that with later CPUs in the TI universe they added an instruction that did something like this but I can't remember where I saw it or how it worked.

 

 

I think it difficult to make a use case for BL/RTWP.  Not impossible.

 

For reducing the amount of memory used by a BLWP vector, you can embed the vector in registers like I did with CPUID:

 

CPUID  DATA $-16            R8 location
       DATA CPUPC           R9 location
CPUPC  BES  12              skip over R10-R15

R0 to R7 are not used by the routine.  R13, R14, and R15 are used normally.  R10, R11, R12 are free.

 

Many routines do not use all 16 registers.  Routines like VWTR, VSBR, VSBW, VMBR, VMBW.

 

Jeff White

  • Like 2
Link to comment
Share on other sites

When I wrote a multitasking thing I had a scheduler, since I didn't want it to be up to the different tasks to decide who would run next. That decision was taken by the scheduler. When task switching was due it was because of a timer interrupt using the TMS 9901 timer. So the scheduler was called by an interrupt, which meant that the information to return to the very same task that was interrupted was in R13-R15 when the scheduler was called.

These three registers I stored in the TIB (Task Information Block) for that task. Then I picked a similar set from the next TIB to be run, inserted into R13-R15 and did the common RTWP to return from the interrupt. But now I was of course returning to another task than the one that was interrupted.

At program start, all TIB had their data block filled with data which would start the task from the first instruction with the proper workspace. So even at program start, the very first task would be called by a RTWP.

 

The scheduler managed a queue of tasks ready to run, where individual tasks could be inserted or removed, depending on the need at any specific moment.

It's of course also possible to use priorities and/or create special event queues, like for tasks waiting for keyboard input, data to show on the screen or whatever it may be.

  • Like 5
Link to comment
Share on other sites

Nice Font You Got There:

image.thumb.png.9b94f0770159082a71876f43a5d02569.png

 

@matthew180 said I should read the first forty pages of the assembler thread. That's been a lot of wading to do. But, there is a lot of good information, especially when Matt was engaged in helping first opry99 and later airshack.

 

  • Like 6
Link to comment
Share on other sites

18 hours ago, dhe said:

Nice Font You Got There:

 

I'm sure I cannot take credit for that font, but I may have tweaked it a little.  With the Classic99 smoothing filter turned on it is hard to know for sure which font it is.  Probably taken from a coin-op game though, I like the way video games did their fonts.

 

18 hours ago, dhe said:

said I should read the first forty pages of the assembler thread. That's been a lot of wading to do

 

Yeah, but when you think of it like a 40 page book, suddenly it seems rather short.  Although content density is probably way higher on the thread than in a book.

 

18 hours ago, dhe said:

But, there is a lot of good information, especially when Matt was engaged in helping first opry99 and later airshack.

 

The best opportunity for helping is when a learner is engaged, asking questions, and following through.  Opry99 did pretty well and got what he needed from assembly, and AirShack (RIP) finished a game in assembly.  IIRC, it was a buck-list of his that he achieved.  It was very rewarding to see their "ah ha!" moments.

  • Like 3
Link to comment
Share on other sites

I'm trying to incorporate a load interrupt handler in to an existing EA3 program.

 

Traditionally, I've noticed, AORG is used for the thing.

 

@mizapf code is:

      AORG >FFFC
      DATA WORKSP,HANDL
 
HANDL CLR  @>FFFC
      LWPI WORKSP
      LI   R0,DELAY
      DEC  R0
      JNE  $-2
      LI   R0,WORKSP
      MOV  R0,@>FFFC
     

Doesn't that tell the loader, to start loading all following code at >FFFC?

 

 I didn't want to mess with the loading of additional code after >FFFC, I though it would work if I manually primed the two address needed.  It doesn't seem to work, when I tell Classic99 to do a load interrupt, my existing program just continues on it's merry way. I don't have a 'real' TI setup to test with.  Does a problem with the code jump out at anyone?

 

liwsp  BSS  >20    Workspace to be used by load interrupt routine.

* TI's E/A loader clears the screen for you, and starts in 32 column GRPI mode.
X      LWPI USRWS
       limi 0

*  >FFFC WP for Load Interupt
*  >FFFD Start Address for PC
       mov  @liwsp,@>FFFC
       mov  @HANDL,@>FFFD

       BL   @DISFNT
       
.. Later on in the routine, a routine with HANDL is defined.
HANDL   EQU   $
    Stuff, 
    More Stuff.
    JMP   $

 

 

 

  • Like 2
Link to comment
Share on other sites

This is the problem part:

  >FFFC WP for Load Interupt
*  >FFFD Start Address for PC
       mov  @liwsp,@>FFFC
       mov  @HANDL,@>FFFD

What you doing here is basically putting what is in r0 into fffc and what would be the first opcode for your code into fffd. Which is wrong as well should be fffe

 

Here is the proper code that should work.

 

Li r0,liwsp

Mov r0,@>fffc

Li r0,handl

Mov r0,@>fffe

 

That way you are placing the actual addresses into the load interrupt vector loactions

  • Like 5
Link to comment
Share on other sites

 Much appreciated Gary.

 

  I suspected as much, I still get twisted around with am I moving the Address or the value at the address.

  Luckily, sometimes xas99 will come to my rescue.

 

  Do you have any recommendations, on how to not make the mistake?

 

 Everything works now, as expected:

image.png.41a8fb8b6a3c6becb0031390a4d7b387.png

 

image.png.76f82dbaf1876ea06ce1fd349ea73602.png

 

  • Like 2
Link to comment
Share on other sites

8 minutes ago, dhe said:

 Much appreciated Gary.

 

  I suspected as much, I still get twisted around with am I moving the Address or the value at the address.

  Luckily, sometimes xas99 will come to my rescue.

 

  Do you have any recommendations, on how to not make the mistake?

 

 Everything works now, as expected:

image.png.41a8fb8b6a3c6becb0031390a4d7b387.png

 

image.png.76f82dbaf1876ea06ce1fd349ea73602.png

 

The second thing you noticed in the sample code they clr @>fffc at the start and then lwpi the workspace which should have already been set and then restore fffc at the end of the interrupt routine.

 

The reason for that is incase of bounce from a physical button with the routine being called more than once. This is basically a software debounce plus stopping it from having multiple load interrupts, with the user smashing the button a few times.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

The other thing you can do -- remember the Classic99 debugger can show you the contents of memory. You can view the contents of FFFC after loading it to make sure it has the values you expect. :)

 

Glad you made it work! You might be the first person to actually use that debug message.

 

  • Like 3
Link to comment
Share on other sites

Posted (edited)
On 5/4/2024 at 11:12 AM, dhe said:

Do you have any recommendations, on how to not make the mistake?

I would say,  think in terms of effective address and operand.  For source and destination
 

The effective address is where the CPU will fetch a word from for the source or destination. Every addressing mode is decoded to an effective address first (Immediate is a special case) as in this table:

 

 
 

addressing mode     effective address 
register  Rn       WP + 2*n
indirect *Rn       word in Rn
symbolic @>FFFC    >FFFC
         @LABEL    address of LABEL
indexed  @TBL(R5)  address of TBL + word in R5

 

If you think of the  operand as always the effective address of source (or destination),  not the value, you won't make the mistake of supposing that the hex you see (like @>FFFC) is the value you're using. It's always going to be the  address of the thing.


From another point of view, every source or destination addressing mode is decoded into an effective address in memory,  not a value.  The value always has to be fetched after that address is decoded. 

 


Now the special case in Load Immediate (or Add Immediate, etc) 

 

LI R10,>1000

 

The source operand is the value >1000. But 

what is the  effective address of >1000? 

 

The definition of Immediate mode is: the source operand is the next word pointed to by the PC.  So there: the effective address is the value of PC.  And Immediate still  fits into the  definition of effective address.

 

If you know that PC is called a hardware register, Immediate Mode is  another Register Indirect AutoIncrementing:

*PC+

 

Addr Data Comment
PC   020A opcode for Load Immediate into R10
PC+2 1000 the value comes after the instruction

 


 


 

It's helpful to study the "9900 Instruction Set" for  definitions of the shorthand  S,D, WP,  SA and so on. 

 

 

Hope this clarifies. Hope it does not confuse further! 
 

 

Edited by FarmerPotato
fix opcode 020A
Link to comment
Share on other sites

@Tursi write: "You can view the contents of FFFC after loading it to make sure it has the values you expect."

 

Yes, I use Classic99's Debugger all the time to view address for values I'm expecting, the quirky problem for me in this was, I didn't know *WHAT* value I expected as this time, the value was created by where the linker loaded something in memory, vs, something more simple like a   FLAG  DATA  >0001.  For a new guy, this was a 3rd order problem. I'm just glad, on my own, I made the connection, that I could trap the interrupt without the need to aorg anything. At three hours of free time a week, I'm still very much at the why didn't this assemble phase! 😃 

 

 Next step, without Gary kind help, would have been to look at the list and try to find unique values in memory and then look through the address display and find 'values you expect'.

 

    The exercise once again shows how helpful Classic99's debugger is, as a learning tool.

  • Like 1
Link to comment
Share on other sites

2 hours ago, dhe said:

I'm just glad, on my own, I made the connection, that I could trap the interrupt without the need to aorg anything.

Than you wont like this...:lol:

 

If you're not going to reassert the VECTOR's values from within the program, letting the LOADER handle this is more efficient than the LIs, and MOVs...

image.thumb.jpeg.86466f846833d532d9b982ad4516aec8.jpeg

...HANDL, HI, BYE, will all take the value of the next line that generates code(JMP $)(>0024) ...same as for HANDL EQU $.

image.thumb.jpeg.ac748359a10d65dc670f42df82a27083.jpeg

image.thumb.jpeg.3a127db88991045d6b5fcc0dd1ff4f2c.jpeg

image.thumb.jpeg.7fcc06f2a195ac9778723e43bd23ed49.jpeg

INSTRUCTIONS, and VECTORS, always begin on an EVEN address, and end on an ODD address.

  • Like 1
Link to comment
Share on other sites

2 hours ago, HOME AUTOMATION said:

If you're not going to reassert the VECTOR's values from within the program, letting the LOADER handle this is more efficient than the LIs, and MOVs...

but won't work once you convert it to a memory image file (option 5).

  • Like 1
Link to comment
Share on other sites

Every time I hit a bump with my understanding of an instruction, I document the problem in my dhe's problem assembler program, here is what this example looks like:

	   def	X
ws	   bss	32
myval  equ  >0072
C99BRK DATA >0113
X      equ	$
       lwpi ws

* look at mov that caught me up in load interrupt
       data >0113
       mov  @myval,@>ff00
* this becomes mov @>0072,@>ff00 -
*    Move the value @ hex 0072, to the address hex ff00...
       li   r1,myval
       mov  r1,@>ff02
* correct - puts the value of >0072 into r1, move the value 72, into memory
*            location >ff02
       jmp  $

 

Then every couple of weeks, I review my problem child programs.

  • Like 3
Link to comment
Share on other sites

55 minutes ago, dhe said:

Every time I hit a bump with my understanding of an instruction, I document the problem in my dhe's problem assembler program, here is what this example looks like:

	   def	X
ws	   bss	32
myval  equ  >0072
C99BRK DATA >0113
X      equ	$
       lwpi ws

* look at mov that caught me up in load interrupt
       data >0113
       mov  @myval,@>ff00
* this becomes mov @>0072,@>ff00 -
*    Move the value @ hex 0072, to the address hex ff00...
       li   r1,myval
       mov  r1,@>ff02
* correct - puts the value of >0072 into r1, move the value 72, into memory
*            location >ff02
       jmp  $

 

Your dedication is inspiring. 

 

I would change this comment as below for more clarity.

* correct - load literal number >0072 into R1. Move the contents of R1 into memory location >FF02

It might seem pedantic but thinking about these things in the most accurate way allows you to use them without surprises.

 

The reason I specified "literal number" is because it is also possible to load a "literal address" into a register. 

 

Example: This code would do the same as yours and might be used where you wanted to repeatedly put a number into a specific address location ASAP, so you hold the address in a register.

       li   r1,myval      a number 
       li   r2,>FF02      an address 
       mov  r1,*r2        move the number in R1 into the address location held by R2
  • Like 2
Link to comment
Share on other sites

Yet another trick:

 

If you set the WP to FFE0, then R14 is at FFFC  and you can use LI. 

 

LWPI >FFE0
LI R14,LIWSP    initial >FFFC
LI R15,LIHAND  initial >FFFE
LWPI LDWS
* continue


It streamlines execution.  Uses 6 memory words, same as the two MOV.  No big reason to use it though. 

  • Like 2
Link to comment
Share on other sites

@HOME AUTOMATION thanks for the tip. I was trying to stay away from org and seg directives, that will be a whole other learning exercise.

 

I have you folks to help me along, I have Classic99 which makes it so much easier to expose the inner working of the 4a then I would with real hardware.

 

I have to wonder, what percent of people purchased the E/A Module with the intent of learning assembly language and just gave up?

  • Like 2
  • Sad 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...