Jump to content
matthew180

Assembly on the 99/4A

Recommended Posts

Ahhh.i was trying to do everything on the @GADR data..lol .. meaning without putting the address in a reg. 

Thank you.

Edited by GDMike

Share this post


Link to post
Share on other sites
2 hours ago, GDMike said:

Ahhh.i was trying to do everything on the @GADR data..lol .. meaning without putting the address in a reg. 

Thank you.

 

EQU does not create data you can change in your program. It is an assembler directive that creates a constant known only at assembly time. It just makes it easier to read your program.

 

...lee

 

  • Like 1

Share this post


Link to post
Share on other sites
5 hours ago, GDMike said:

Ahhh.i was trying to do everything on the @GADR data..lol .. meaning without putting the address in a reg. 

Thank you.

This would also work:

MOV @GADR,R1
MOV @GADR+2,R2
MOV @GADR+4,R3
...

But the resulting machine code is longer.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, Asmusr said:

This would also work:

MOV @GADR,R1
MOV @GADR+2,R2
MOV @GADR+4,R3
...

But the resulting machine code is longer.

 

Interestingly, despite being larger, I think it would end up being slightly faster since it avoids a memory access to update the indirect pointer, if I understood the cycle count chart correctly.  So, if you just have a few items at a fixed address, the larger codesize may justify the unrolled approach.

 

Indirect access with post-increment works better in loops though, especially loops whose trip count isn't known until run-time.

  • Thanks 1

Share this post


Link to post
Share on other sites
7 hours ago, intvnut said:

 

Interestingly, despite being larger, I think it would end up being slightly faster since it avoids a memory access to update the indirect pointer, if I understood the cycle count chart correctly.  So, if you just have a few items at a fixed address, the larger codesize may justify the unrolled approach.

 

Indirect access with post-increment works better in loops though, especially loops whose trip count isn't known until run-time.

 

Also, if your registers are in PAD (e.g. >8300) then arguments in registers save a lot of cycles. Fetching the address from 8-bit memory costs 6 cycles after wait states, but fetching a register is 1 memory access.

* Really fast code assuming registers in PAD
LI     R9,GADR
STWP  R0
INCT   R0            * set pointer to R1.. could be LI R0,MYWS+2 or LI R0,>8302
MOV *R9+,*R0+   * copies to R1
MOV *R9+,*R0+   * copies to R2
MOV *R9+,*R0+   * copies to R3

 

  • Thanks 1

Share this post


Link to post
Share on other sites

When I was doing a lot of programming on my TI 99/4A, I usually wrote everything that was doable in Pascal in - you get it - Pascal. There are a few things you can't access, like CRU bit etc., but most things can be done in Pascal.

Then, when the program was debugged and the idea found feasible, I evaluated if there were segments of code that delayed the result too much, so that they were worth the effort of converting them to assembly language. If so, I did that, thus keeping most (hopefully) of the program in the easier-to-use Pascal and only converting the parts that really makes a difference to assembler.

 

I've never seen the need to write programs that necessarily are only one or another language, when you can use more than one in the same application, each to its benefit.

There is, for example, rarely any improvement in reading data the user is typing in by an assembly program. The user is so slow anyway, that you can do that on a higher level. But processing the data, there you may gain something from going the assembly way.

  • Like 2

Share this post


Link to post
Share on other sites

hello, new to this sub forum. I am trying to assemble the first program on this page. it is asking for the source file, object code and device name.

Source code is Dsk1.save no object code, no idea what device name means. any help would be appreciated. Using classic99

  • Like 1

Share this post


Link to post
Share on other sites
32 minutes ago, Samuel Pedigo said:

hello, new to this sub forum. I am trying to assemble the first program on this page. it is asking for the source file, object code and device name.

Source code is Dsk1.save no object code, no idea what device name means. any help would be appreciated. Using classic99

The "DSK1." part of the file name is the device name.  The object code is the device name and file name to which the output of the assembler will be stored.  You probably have the option to send that output to an alternate device with Classic99, like "DSK2", "DSK3" or another device supported by the emulator.

  • Like 1

Share this post


Link to post
Share on other sites
49 minutes ago, Samuel Pedigo said:

hello, new to this sub forum. I am trying to assemble the first program on this page. it is asking for the source file, object code and device name.

Source code is Dsk1.save no object code, no idea what device name means. any help would be appreciated. Using classic99

 

I presume you are using the Editor/Assembler cartridge.  The source file is the file with the source code. If this is the first instance, it will be the name for saving the source code you type into the editor. The object code is the name of the file where you want the Assembler’s output saved. You should use all capital letters for file and device names. In Classic99, you can save Windows names such as file.obj or file.txt without regard to case or name length, but it is still a good idea to use all uppercase and less than or equal to 10 characters for the filename portion if you intend to use it on real iron, e.g., DSK1.XYZ4567890. And, of course, what @hhos said.

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
On 7/10/2019 at 10:34 AM, FarmerPotato said:

 

Also, if your registers are in PAD (e.g. >8300) then arguments in registers save a lot of cycles. Fetching the address from 8-bit memory costs 6 cycles after wait states, but fetching a register is 1 memory access.

* Really fast code assuming registers in PAD
LI     R9,GADR
STWP  R0
INCT   R0            * set pointer to R1.. could be LI R0,MYWS+2 or LI R0,>8302
MOV *R9+,*R0+   * copies to R1
MOV *R9+,*R0+   * copies to R2
MOV *R9+,*R0+   * copies to R3

 

Just want to say, Thank you for straightening me out on this routine! I've almost got a complete package operational. I was practically giving up on assy until you guys pointed out how to handle my BL and how to save R11. Because all the while I thought a simple RT would handle R11 on its own. Sometimes my BL would work but as my program became larger I would lose control. Big difference in how my program is running these days ..my KSCAN probs just boiled down to BL probs as mentioned above. But I'm a happy camper these days!!

Share this post


Link to post
Share on other sites

It's cool when the light comes on isn't it? 🙂

 

FYI: On other CPUs the equivalent of a branch and link instruction (ex: CALL in x86 code) automatically saves the return address for you in chunk of memory call the "stack"

There is a special register that holds the address of the "stack" and it automatically saves "R11" (so to speak) and moves the stack register for you. And when you do a return it automatically gets the old "R11" and moves the stack register back for you.

 

The 9900 was created before a lot of people realized the power of a stack "built-into" the CPU however you can do it yourself by dedicating a register for the job. It's actually described in a book I have called "Software Development Handbook" by Vincent and Gill, Texas Instruments 1981.

 

It just requires a bit of discipline on the part of the coder.  In the native code compiler I am working on I decided to begin every sub-routine with

         SP DECT,
         MOV R11,*SP

where SP is the register I use for the stack pointer (pick your favourite)

And RT is replaced by a two instruction "pseudo-instruction" (you can do these things when you have the source code for the Assembler program) 🙂

I call the new pseudo-instruction RET to avoid confusion and it does:

        MOV *SP+,R11
        B   *R11

By using this format I can nest sub-routines deeply and the overhead is not too bad.

 

The other part to remember is to initialize your "SP" register to a place in memory that you reserve for that purpose. Typically 32 bytes would be enough which provides 16 levels of nesting.

 

  • Like 1

Share this post


Link to post
Share on other sites

Oh..yes I see now. On another note, I know how to address a memory location, and increment it by the stack pointer. But how do I increase the label of memory to point to like 80 words in advance. Like if I have a label: BNK EQU >A000

And LI R15,80

And I want to add a register that contains 80 as in BNK+80.

I tried Ai @BNK,R15 

I need BNK to look like BNK+80

But my register will vary in number so I can't just say BNK+80.

Thanks.

Edit: I tried Ai *BNK,R15 typo

 

Edited by GDMike

Share this post


Link to post
Share on other sites
3 hours ago, GDMike said:

Oh..yes I see now. On another note, I know how to address a memory location, and increment it by the stack pointer. But how do I increase the label of memory to point to like 80 words in advance. Like if I have a label: BNK EQU >A000

And LI R15,80

And I want to add a register that contains 80 as in BNK+80.

I tried Ai @BNK,R15 

I need BNK to look like BNK+80

But my register will vary in number so I can't just say BNK+80.

 

I believe the following would do:

   LI R15, 80    ; or some other value, perhaps computed into R15
   AI R15, BNK   ; add the address of your label to the value
   ; Now R15 = BNK + 80 (or BNK + whatever was in R15).  Use *R15 to access whatever.

 

Edited by intvnut
clarification, fix syntax error

Share this post


Link to post
Share on other sites

Here is a (tested) example of using a stack to store return addresses.

 

R10 will be my stack pointer.

The stack area is reserved by a STACK BES 2*8 directive so it is big enough for 8 words.

BES is like BSS but it sets the label equal to the *end of* the reserved area  (the address after).

Because we are going to be counting down from STACK.

 

BSS and BES reserve data areas, mixed with your assembled code. In a modern computer, these areas would be somewhere in memory far away from the code.

 

In the program, START calls FOO. FOO calls BAR (3 times). BAR calls VMBW.  VMBW calls SETVA.

 

START -> FOO -> BAR -> VMBW -> SETVA

 

Each of these "pushes" R11 on the stack, then "pops" it back to R11 for its return.

(remember RT  just means B *R11)

Even START is pushing the R11 given to it. 

 

       DEF START
 
VDPWD  EQU >8C00
VDPWA  EQU >8C02
VDPRD  EQU >8800
VDPSTA EQU >8802
 
* BES equates STACK to the address after 8*2 bytes. Opposite of BSS.
STACK  BES 8*2   up to 8 levels deep
 
* initialize stack pointer
START  LI R10,STACK
 
       DECT R10             save given R11 on stack. it returns back to E/A
       MOV  R11,*R10
 
* call something
       BL   @FOO
 
       BL   @WAIT
 
* exit program
       MOV *R10+,R11
       RT
 
 
 
FOO    DECT R10             save R11 on stack
       MOV  R11,*R10
* call BAR
       LI   R0,34
       BL   @BAR
       LI   R0,144
       BL   @BAR
       LI   R0,254
       BL   @BAR
* return from FOO
       MOV *R10+,R11
       RT
 
 
BAR    DECT R10             save R11 on stack
       MOV  R11,*R10
 
       LI   R1,HELLO
       LI   R2,HELLO#
       BL   @VMBW
 
* return from BAR
       MOV *R10+,R11
       RT
 
* the usual multiple byte write
* R0 vdp address
* R1 data address
* R2 length
VMBW   DECT R10
       MOV  R11,*R10
       ORI  R0,>4000
       BL   @SETVA
VMBW1  MOVB *R1+,@VDPWD
       DEC  R2
       JNE  VMBW1
* return from VMBW
       MOV *R10+,R11
    RT
 
* single byte R1, multiple times
* R0 vdp address
* R1 byte to fill with
* R2 length
VSBMW  DECT R10
       MOV  R11,*R10
       ORI  R0,>4000
       BL   @SETVA
VSBMW1 MOVB R1,@VDPWD
       DEC  R2
       JNE  VSBMW1
* return
       MOV *R10+,R11
       RT
 
* SETVA won't be calling any subroutines so it doesn't save R11 on stack
SETVA  SWPB R0
       MOVB R0,@VDPWA
       SWPB R0
       MOVB R0,@VDPWA
       RT

* wait pushes and pops R11 just to make a point
WAIT   DECT R10
       MOV  R11,*R10
       SETO R0         big number
       DEC  R0
       JNE  $-2        i like no labels
 
       MOV  *R10+,R11
       RT
 
 
HELLO  TEXT 'HELLO'
HELLO# EQU  $-HELLO
       EVEN
       END

 

  • Like 2

Share this post


Link to post
Share on other sites
1 hour ago, GDMike said:

Oh..yes I see now. On another note, I know how to address a memory location, and increment it by the stack pointer. But how do I increase the label of memory to point to like 80 words in advance. Like if I have a label: BNK EQU >A000

And LI R15,80

And I want to add a register that contains 80 as in BNK+80.

I tried Ai @BNK,R15 

I need BNK to look like BNK+80

But my register will vary in number so I can't just say BNK+80.

Thanks.

 

 

I think immediate operands(AI) can only be loaded into a workspace register.

ADD WORDS(A) can handle two general addresses... meaning a workspace register or a symbolic address(@)...

 

       A    R15,@BNK

 

* if >A000=>00 and >A001=>00

* this would make >A000=>00 and >A001=>50

 

...not sure how this equates to "80 words in advance" though.

 

Label values are resolved at assembly time/can't be changed by the running program.

Edited by HOME AUTOMATION
clarity
  • Like 1

Share this post


Link to post
Share on other sites
18 hours ago, FarmerPotato said:

Here is a (tested) example of using a stack to store return addresses.

 

R10 will be my stack pointer.

The stack area is reserved by a STACK BES 2*8 directive so it is big enough for 8 words.

BES is like BSS but it sets the label equal to the *end of* the reserved area  (the address after).

Because we are going to be counting down from STACK.

 

BSS and BES reserve data areas, mixed with your assembled code. In a modern computer, these areas would be somewhere in memory far away from the code.

 

In the program, START calls FOO. FOO calls BAR (3 times). BAR calls VMBW.  VMBW calls SETVA.

 

START -> FOO -> BAR -> VMBW -> SETVA

 

Each of these "pushes" R11 on the stack, then "pops" it back to R11 for its return.

(remember RT  just means B *R11)

Even START is pushing the R11 given to it. 

 

       DEF START
 
VDPWD  EQU >8C00
VDPWA  EQU >8C02
VDPRD  EQU >8800
VDPSTA EQU >8802
 
* BES equates STACK to the address after 8*2 bytes. Opposite of BSS.
STACK  BES 8*2   up to 8 levels deep
 
* initialize stack pointer
START  LI R10,STACK
 
       DECT R10             save given R11 on stack. it returns back to E/A
       MOV  R11,*R10
 
* call something
       BL   @FOO
 
       BL   @WAIT
 
* exit program
       MOV *R10+,R11
       RT
 
 
 
FOO    DECT R10             save R11 on stack
       MOV  R11,*R10
* call BAR
       LI   R0,34
       BL   @BAR
       LI   R0,144
       BL   @BAR
       LI   R0,254
       BL   @BAR
* return from FOO
       MOV *R10+,R11
       RT
 
 
BAR    DECT R10             save R11 on stack
       MOV  R11,*R10
 
       LI   R1,HELLO
       LI   R2,HELLO#
       BL   @VMBW
 
* return from BAR
       MOV *R10+,R11
       RT
 
* the usual multiple byte write
* R0 vdp address
* R1 data address
* R2 length
VMBW   DECT R10
       MOV  R11,*R10
       ORI  R0,>4000
       BL   @SETVA
VMBW1  MOVB *R1+,@VDPWD
       DEC  R2
       JNE  VMBW1
* return from VMBW
       MOV *R10+,R11
    RT
 
* single byte R1, multiple times
* R0 vdp address
* R1 byte to fill with
* R2 length
VSBMW  DECT R10
       MOV  R11,*R10
       ORI  R0,>4000
       BL   @SETVA
VSBMW1 MOVB R1,@VDPWD
       DEC  R2
       JNE  VSBMW1
* return
       MOV *R10+,R11
       RT
 
* SETVA won't be calling any subroutines so it doesn't save R11 on stack
SETVA  SWPB R0
       MOVB R0,@VDPWA
       SWPB R0
       MOVB R0,@VDPWA
       RT

* wait pushes and pops R11 just to make a point
WAIT   DECT R10
       MOV  R11,*R10
       SETO R0         big number
       DEC  R0
       JNE  $-2        i like no labels
 
       MOV  *R10+,R11
       RT
 
 
HELLO  TEXT 'HELLO'
HELLO# EQU  $-HELLO
       EVEN
       END

Do the new assemblers allow you to rename registers and do they support macros?  With those two features you can make the stack code look very explicit.

However I realize that some people want to see "behind the curtain" at all times so it's not everyone's preference.

 

 

Share this post


Link to post
Share on other sites

TheBF said

Quote

Do the new assemblers allow you to rename registers and do they support macros?  With those two features you can make the stack code look very explicit.

However I realize that some people want to see "behind the curtain" at all times so it's not everyone's preference.

 

SP     EQU 10

Does the job of renaming.

R10 is just an equate anyway, enabled by the R option in the original 99/4 assembler.  MOV *10+,11 is the eventual syntax.

RAG assembler and GenAsm had macros.

 

I see ralph xas99 (from xdt99) has macros. Here I go with xas99 macros. Untested code:

 

* initstack
      .defm spinit
sp    equ 10
      li  sp,stack

* push R11 onto stack
      .defm savert
      dect sp
      mov  r11,*sp
      .endm
      
* pop from stack into R11 and return
      .defm return
      mov  *sp+,r11
      rt
      .endm

* generic push <arg>
      .defm push
      dect sp
      mov  #1,*sp
      .endm
      
* generic pop <arg>
      .defm pop
      mov  *sp+,#1
      .endm
      
* rewritten savert, return
      .defm savert
      push r11
      .endm
      
      .defm return
      pop r11
      rt
      .endm

      
* Example. 
* Call chain: start -> foo -> bar. 
* Bar is polite: it saves/restore the register it uses, R12.
* That is another use for stack push/pop, given that the stack is deep enough.

start:
       spinit
       pushrt
       bl  @foo
       return
foo:   
       pushrt
       bl  @bar
       return
       
bar:   
       push r12
       li   r12,>1300
       sbo  0
       pop  r12
       
       rt

 

  • Like 1

Share this post


Link to post
Share on other sites

The problem is that the TMS99xx processors do not have a fixed stack pointer register; that is, changing workspaces also means losing the current value of the stack pointer.

 

I thought stack pointers were more common in earlier days, i.e. before the TMS99xx. On the other hand, the instruction set architecture of the TMS99xx goes back to the TTL-based TI990 from 1973, and the Intel 8008 was introduced just a year before.

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, mizapf said:

The problem is that the TMS99xx processors do not have a fixed stack pointer register; that is, changing workspaces also means losing the current value of the stack pointer.

 

I thought stack pointers were more common in earlier days, i.e. before the TMS99xx. On the other hand, the instruction set architecture of the TMS99xx goes back to the TTL-based TI990 from 1973, and the Intel 8008 was introduced just a year before.

Yes that can be minor inconvenience, but if your BLWPing out to new workspace then you probably want that freedom no?

In Forth we manage 2 stacks and when I BLWP out of Forth workspace its because I don't need a stack but I want to use registers for parameters that stay with the routine when I leave and come back to it later. Seems to work well.

 

I haven't done much research on the topic of early CPU hardware stacks, but I know IBM 360 and earlier did not have a hardware stack.

I believe it was the uP guys who saw the value early on and built stack instructions into their machines. (reference needed)

 

I know that the Burroughs made the B5000 in the 1960s. It was a pure stack machine ie, no registers and that Chuck Moore the inventor of Forth did some early work with that machine which influenced his later work.  But it was the only one in its day I think and was very different than a register machine with push/pop instructions.

 

 

  • Like 1

Share this post


Link to post
Share on other sites

What I wanted to point out is that if we want to set up something like a stack with a register as stack pointer, this does not mix well with the BLWP concept. TI should have kept such a stack pointer outside of the R0-R15 set, but it is easy in hindsight to say what one should have done. 🙂

  • Thanks 1

Share this post


Link to post
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.

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...