Jump to content






- - - - -

TI ASM Battles: What?! No stack?!

Posted by gregwarner, 24 February 2013 · 987 views

Just spent the last two days battling my old TI computer's assembler. I've been attempting to write a simple program that would print some memory addresses on the screen so I could probe around in the little guy's mind. But for the life of me, I couldn't figure out why my relatively simple code was crashing the system.

Until it hit me.

There is no stack.

What a minute? A computer without a stack? Yep. The TI has no stack pointer register. What wizardry is this, you might ask? Well, let me explain.

The TMS9900 chip doesn't have a stack pointer register, so if you want a call stack, you must implement one yourself. (In fact, there is a stack already implemented in the machine's ROM OS, but it's written in a language known as GPL, which is compiled to byte code and executed on a virtual machine in the console's ROM, so I want to avoid using that as much as possible.)

Here's the offending code that seemed to defy all reasoning: (Highly simplified here.)
...        (In main routine.)
...
       BL   @PRINTW    (Branch and Link to PRINTW routine.)
...
...


           (PRINT Word routine. Prints a word of memory to the screen in hex.)
PRINTW ... (do some stuff...)
       ...
       BL   @TOASC     (Branch and Link to TOASC routine.)
       ...
       ... (do some more stuff and print the ascii chars to the screen.)
       ...
       RT  (Return to calling routine.)


           (TO ASCii routine. Converts a binary number in a register into ASCII chars, using hex notation.)
TOASC  ...
       ... (do some stuff...)
       ...
       RT  (Return to calling routine.)
Seems simple enough, right? In my main routine, I call a subroutine called PRINTW several times to print a word of memory to the screen. PRINTW in turn calls the TOASC subroutine several times during the course of its operations to convert binary integers into their ASCII equivalents.

Now, if there was a hardware stack, this would work flawlessly. But, as I should have known, there is no hardware stack in the TI.

The way BL and RT work is: BL places the address of the next instruction following it into R11, and then loads the source operand into the Program Counter, thereby transferring control to the subroutine. RT simply performs the reverse. It places the value stored in R11 back into the PC, thereby returning control back to the calling routine.

However, all this is performed without a context switch, so the calling routine and the subroutine are sharing a set of registers. This means, if the subroutine in turn calls another subroutine, it also uses R11, overwriting the previous value, losing it forever. Now we have no way of returning to the top level parent routine.

To hack together a simple solution, I opted to write what I guess can be analogous to a call-linked-list rather than a call stack. Every routine in my program now has a word of memory which it uses to store R11 upon entry, and it's duty is to restore this register before calling RT. This way, it can call all the subroutines it wants.
...        (In main routine.)
...
       BL   @PRINTW    (Branch and Link to PRINTW routine.)
...
...


           (PRINT Word routine. Prints a word of memory to the screen in hex.)
PRTRET DATA >0000  (Storage space for PRINTW's return pointer.)
PRINTW MOV  R11,@PRTRET  (First thing we do is store the return pointer!)
       ... (do some stuff...)
       ...
       BL   @TOASC     (Branch and Link to TOASC routine.)
       ...
       ... (do some more stuff and print the ascii chars to the screen.)
       ...
       MOV  @PRTRET,R11  (Restore the return pointer.)
       RT  (Return to calling routine.)


           (TO ASCii routine. Converts a binary number in a register into ASCII chars, using hex notation.)
TOARET DATA >0000  (Storage space for TOASC's return pointer.)
TOASC  MOV  R11,@TOARET  (First store the return pointer!)
       ...
       ... (do some stuff...)
       ...
       MOV  @TOARET,R11  (Restore the return pointer.)
       RT  (Return to calling routine.)
Truthfully, the TOASC routine doesn't need to explicitly store its return pointer, since it doesn't call any further subroutines, however, I elected to put it in there in case I decide to change things later.

So how does this solution fare? Well, it works. However, it's not ideal. One problem is that it doesn't allow for recursive subroutines, since each routine only has one space to store its return pointer. So a side effect of doing it this way is that each subroutine may only be called once, and must terminate before it may be called again.

So what have I learned? RTFM. :) But seriously, it's interesting to see how older systems handled things differently. The TI is very different from what I'm accustomed to, first with the working registers residing in main RAM instead of the CPU, and now having to juggle your own return pointers. From what I read, there's a lot of love in the TI community for this assembly language, although so far, I'm finding the kind of affection I feel for it to be a lot like the affection you feel toward a quirky, 3-legged dog. It's awkward, but you feel obligated to treat it nicely.

Next time: Greg implements a call stack!

  • 1



Perhaps the virtual machine may be adjusted to be compiled once, then executed in subsequence absent re-compilation. This may probably resolve the time-complexity issues you fear.

    • 0

Trackbacks for this entry [ Trackback URL ]

There are no Trackbacks for this entry