Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

 

A simple resistor and capacitor on the switch tied to the LOAD input would have provided some simple hardware debounce and the software loop would probably not be necessary.

 

The problem with this approach for Vorticon's purposes is that the LOAD is being driven by a microcontroller, so the input is not bouncing.

 

If that is the case then a timeout is not going to solve this, interrupts will continue to come. Several things come to mind:

 

1. Disable interrupts immediately upon entering the routine, and re-enable them when you are done. Keep in mind though, that if the interrupts are coming in fast and furious, it could easily overwhelm the 99/4A. As soon as you are done with the routine and re-enable interrupts, another could be right there waiting.

 

2. Modify the SmallyMouse code to limit the interrupt rate. The code is available, I had to compile it myself and load it on the microcontroller.

 

3. You could put a triggered one-shot between the interrupt and the 99/4A to limit the interrupt rate. Once an interrupt triggers the one-shot, any more interrupts are blocked until the one-shot timeout, which you could adjust to something sensible (once every 30ms or so would be a mouse update every two video frames).

 

You are absolutely correct Matt. I just thought the debouncing in software trick was neat.

Unfortunately, there is no way to disable the LOAD interrupt as it is umaskable. As for modifying the SmallyMouse code, it would be easier to just use an arduino with USB support like the Teensy++ and just decode the USB packets coming from the mouse (they have a standard format) and output direct positional data to a few pins without having to worry about quadrature encoding, phase shifts and all that messy stuff. That way a simple connection to the PIO port will work perfectly. This is actually what I want to experiment with next. The SmallyMouse was really designed to connect to vintage computers already equipped with a mouse port (Amiga, Atari ST, Electron etc...) and I have found that it's more pain than it's worth to try adapting it to mouseless systems. As to your third option, it requires additional hardware which would defeat the purpose...

Link to comment
Share on other sites

Yeah, I think I was attracted to the device for coin-op purposes, since it outputs the same signals as an arcade-style track-ball. But unless you have a system designed to accept that type of input, then it is probably more trouble than it is worth, as you pointed out.

Link to comment
Share on other sites

The LOAD interrupt on the 9900 /is/ level triggered, but after any interrupt you get one instruction before interrupts are enabled again - so the CLR is safe.

 

We had a chat about this in another thread a few years ago: http://atariage.com/forums/topic/260870-does-anything-use-load-and-iaq-at-the-side-port

 

Classic99 won't let you see the effects of CLRing the workspace vector, as it refuses to trigger a LOAD interrupt if the vector's not filled out, but it will still work since the first trigger happens. ;) I hadn't seen that trick before I coded support. The real console will keep re-triggering the interrupt for as long as the pin is low.

Link to comment
Share on other sites

  • 3 months later...

 

I think >8378 is used by the GPL RAND function to return a random byte. I will check on that. <--Yes, this is correct.

 

>83C0 is the console ISR’s R0 and the random number seed for the GPL RAND function used by TI Basic and RXB. It is also the seed for the Pseudo-Random Number Generators of TI Extended Basic, TI Forth, fbForth and TurboForth (I think). Not sure about Camel99 Forth.

 

...lee

 

 

Looking at these older posts for little Gems.

 

Just to confirm, under the fine tuteledge found here, Camel99 Forth also uses >83C0 to pick up a PRNG seed.

 

In fact it is named "SEED" in the code.

 

See file: https://github.com/bfox9900/CAMEL99-V2/blob/master/LIB.TI/RANDOM.FTH for details

 

B

  • Like 1
Link to comment
Share on other sites

Question for the ASM coders.

 

In Forth Assembler I built an integrated BLWP vector that puts the code and the vector together. It seems a little cleaner to me.

 

I think the code below would do this in conventional Assembler.

I am wondering if anybody else uses a structure like this or have I just stumbled upon something everybody already knows/does.

 

(code is untested so don't laugh too hard if it's all wrong)

 

Sidenote:

The reason it becomes interesting in Forth Assembler is because you can make the structure run BLWP automatically so SUB-programs like this run just by invoking the label name. Kinda cool , at least for it was for me.

WKSP1  BSS  20

PROG1  DATA WKSP1,$+2
       A    R1,R0
       A    R2,R0
       A    R3,R0
       RTWP
      
PROG2  DATA WKSP1,$+2
       S    R1,R0
       S    R2,R0
       S    R3,R0
       RTWP,
         
      BLWP  PROG1 
      BLWP  PROG2 
Link to comment
Share on other sites

In Assembler, you have to write BLWP @PROG1. Also, the BLWP lines must be part of some longer program code that is called somehow, and later returns somehow. (Note that you need to remove the comma after RTWP.)

 

Noted. @ required. Comma not required.

 

This is just a code fragment to demonstrate the Vector and code being together. So yes a great deal more would be part of whatever program this was part of.

Link to comment
Share on other sites

Would, in Forth, use different workspaces for PROG1 and PROG2? Otherwise you could just use BL.

These examples are more about leaving Forth and doing a sub-program in another workspace with Assembly language and then returning to Forth.

 

And in this case both sub-programs share a workspace so they can keep important information in the registers between calling PROG1 or PROG2.

Link to comment
Share on other sites

In my opinion, the advantage of the vectored addressing is that you can group the vectors together, for easy overview. It means that if you want to write a new implementation of one subroutine, you can place that anywhere, and then, at your central vector pack location, just change the pointer to the new version, or the old version, for testing that they both work (or that the new one doesn't have the same bug as the old one). You don't need to scout around for the different routines to find their vectors.

Especially if the actual code is in different include files, due to total code size, this is valuable.

Link to comment
Share on other sites

So the only line I am actually curious about is:

PROG1  DATA  WKSP1,$+2

Does anybody use this structure?

I learned this exact approach from another programmer during when I first started dabbling in assembly. It wasn't until years later that I came upon some code that used the vector/label, and it dawned on me that I could place that vector "anywhere".

Link to comment
Share on other sites

A variation on BLWP (from FORTI's interrupt service routine)

LI R1,$+8
MOV *SP,R0
BLWP R0
* PC continues here:

BLWP takes WP from R0, PC from R1,

This gets a workspace pointer from the FORTH stack and performs an approximation of LWP, a TMS9995 instruction, on the TMS9900.

 

It's used to run the same routine on different workspace contexts.

 

Afterward you can put a return address in R14.

CLR *SP     set status
LI R14,NEXT
RTWP
Link to comment
Share on other sites

In my opinion, the advantage of the vectored addressing is that you can group the vectors together, for easy overview. It means that if you want to write a new implementation of one subroutine, you can place that anywhere, and then, at your central vector pack location, just change the pointer to the new version, or the old version, for testing that they both work (or that the new one doesn't have the same bug as the old one). You don't need to scout around for the different routines to find their vectors.

Especially if the actual code is in different include files, due to total code size, this is valuable.

 

 

I learned this exact approach from another programmer during when I first started dabbling in assembly. It wasn't until years later that I came upon some code that used the vector/label, and it dawned on me that I could place that vector "anywhere".

 

And course making a table of vectors let's you call them by index which is cool too.

So I guess the moral is that "one size does not fit all".

Link to comment
Share on other sites

 

A variation on BLWP (from FORTI's interrupt service routine)

LI R1,$+8
MOV *SP,R0
BLWP R0
* PC continues here:

BLWP takes WP from R0, PC from R1,

This gets a workspace pointer from the FORTH stack and performs an approximation of LWP, a TMS9995 instruction, on the TMS9900.

 

It's used to run the same routine on different workspace contexts.

 

Afterward you can put a return address in R14.

CLR *SP     set status
LI R14,NEXT
RTWP

 

OH! That is very clever. So by doing BLWP R0 (as opposed to BLWP @R0) you can use the registers as vector addresses.

 

So since my Forth keeps TOS in R4, all I need to do in Forth assembler is...

CODE CALLPROG  (code_address workspace -- )
              *SP+ R5 MOV,
                  R4 BLWP,
                     NEXT,

How cool! I gotta try that right now.

 

Thanks!

Link to comment
Share on other sites

This code worked just as you described.

 

Thanks again FarmerPotato! ;)

\ BLWP direct test

INCLUDE DSK1.TOOLS.F
INCLUDE DSK1.ASM9900.F

HEX
CREATE WKSP2  20 ALLOT

CREATE TEST
       R0 DEAD LI,
       R1 BEEF LI,
       R2 DEAD LI,
       R3 BEEF LI,
       RTWP,

CODE BLWP() ( addr wksp -- )
    *SP+ R5 MOV,  \ pop addr into R5 (temp register in CAMEL99) 
     R4 BLWP,     \ BLWP direct to wksp in TOS cache register
     TOS POP,     \ refill TOS register (R4)
     NEXT,        \ return to Forth 
     ENDCODE

EDIT: I should add

 

Called like this:

TEST WKSP2 BLWP() 
Edited by TheBF
Link to comment
Share on other sites

In XB ROMs this is the standard use of Registers

  2801            ************************************************************
  2802            * CALL - STATEMENT EXECUTION  
  2803            * Finds the subprogram specified in the subprogram table,   
  2804            * evaluates and assigns any arguments to the formal   
  2805            * parameters, builds the stack block, and transfers control 
  2806            * into the subprogram.  
  2807            *  General register usage:  
  2808            *     R0 - R6 Temporaries   
  2809            *     R7      Pointer into formals in subprogram name entry 
  2810            *     R8      Character returned by PGMCHR  
  2811            *     R9      Subroutine stack  
  2812            *     R10     Temporary   
  2813            *     R11     Return link   
  2814            *     R12     Temporary   
  2815            *     R13     GROM read-data address  
  2816            *     R14     Interpreter flags   
  2817            *     R15     VDP write-address address   
  2818            ************************************************************ 

The only time you see R15 or *R15 is like MOVB is

  2830 7528 D7E0         MOVB @R0LB,*R15        Load out the VDP write address
       752A 83E1  
  2831 752C 0260         ORI  R0,WRVDP          Enable the VDP write  
       752E 4000  
  2832 7530 D7C0         MOVB R0,*R15           Second byte of VDP write   

R10 is used mostly for a copy of stack or stack pointer.

R12 is used mostly for a copy of R11 or a secondary return pointer like R11

 

That is the standard approach Texas Instruments had for Register usage you see in many programs that TI wrote.

Edited by RXB
Link to comment
Share on other sites

BL *12 is the same as BL *R12, of course, but I have never seen BL 15 before. Is it the same as BL *R15, or what does it mean?

 

It is valid, but not the same. If your workspace pointer is set to >8300, "BL R15" is the same as "BL @>831E" (branching to the assembly code in memory at the same address as R15.)

 

I wonder what the the C99 compiler was using it for? Maybe a breakpoint or tracepoint where R15 could easily be changed between "B *R11" or "something else" to cause compiled code to do something different dynamically at runtime? Just a guess...

  • Like 2
Link to comment
Share on other sites

Fun fact: The B/BL addressing introduces a minor inconsistency in the TMS9900 microarchitecture. Normally, the operand (source, destination) is first fetched from the given address, whichever mode is used (register, indirect, symbolic, indexed, indirect-increment).

 

In the book "9900 Family System Design", chapter 4, the microprocessing of the commands is explained; I needed that information for implementing the cycle-precise emulation of the 99xx CPUs in MAME and thus stumbled over that comment for B and BL (page 4-96):

 

Note: The source data is fetched, although it is not used.

This actually means that when you do a B @>A000, the CPU indeed first fetches the word from >A000, then sets the program counter to >A000 and continues execution. It would have been more logical to interpret "B R15" as "branch to the address in R15". But instead, it means "branch to the address of R15".

 

The TMS9995 kept the interpretation as in the 9900, of course (would have been incompatible otherwise); but to improve the situation, the previously used "data derivation sequence" was modified to be an "address derivation sequence", which could avoid to fetch unneeded data.

  • Like 3
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...
×
×
  • Create New...