Jump to content
Hans23

VBLANK in TurboForth

Recommended Posts

Hi,

 

is there a way to synchronize to VBLANK in TurboForth?  A counter would be useful, or maybe an efficient way to wait for the vertical blanking interrupt to occur.

 

Any hints would be greatly appreciated.  Thanks!
-Hans

Share this post


Link to post
Share on other sites

Well... I don't know what VBLANK does but based on the name I suspect it fills a section of VDP RAM with "blank" chars. (>20) 

 

I read this online:

Bit 0 (weight >80). This bit is set to 1 at the end of the raster scan of the last line of the display area (i.e. before drawing the bottom margin).

 

So we can do this to wait for that bit. 

I used @ instead [email protected] because it's faster. It just means we get the data in the other byte of the Forth data stack. No biggy.

Then we mask the bit out of the rest of the data with 8000 AND.   UNTIL will loop until that bit is not zero.

 

HEX
: WAITVDP  BEGIN 8802 @ 8000 AND UNTIL ; 

You could then redefine VBLANK like this:

: VBLANK  ( addr len --)  WAITVDP  VBLANK ;

If you don't want the extra name in the dictionary you could put WAITVDP code inline. But having it named means you can use it anywhere you need it later

There a small amount of time used to call it by name but Forth does that pretty quickly.

 

If absolute speed was needed it would not be much more code to write WAITVDP in Forth Assembler.

 

People with more hardware knowledge here can chime in to see if this is correct usage, but it's Forth. Try it and see what happens.  :) 

 

  • Like 1

Share this post


Link to post
Share on other sites

It's worth noting that direct polling of the status bit has some caveats. If the console interrupt routine is on (I think it is in TurboForth), then you will interfere with it triggering (every read of the status register also clears the blank bit). This means you will sometimes miss it (if the console gets it first), and you will make the console miss sometimes too, which may cause sounds or sprites to be slowed down. There's even a race in the 9918 itself that might cause neither of you to get it, but this is pretty rare.

 

Since the console interrupt is on in TurboForth (I believe), you can monitor the mirror of the status register at >837B (byte, copied by the console interrupt), or the screen blank counter at >83D6 (word, counts by two every frame that a key is not pressed). If you want to track it directly in the hardware, bit 2 on the CRU has fewer issues than reading the status byte directly, but there is still a chance of missing it if the console gets it first. (However, there is no chance of stealing it from the console).

 

VBLANK is just one of the many terms for the end of frame indicator, it's not a separate action itself.

 

  • Like 4

Share this post


Link to post
Share on other sites
31 minutes ago, Tursi said:

If the console interrupt routine is on (I think it is in TurboForth), . . .

 

Interrupts are normally ON in TI Forth and fbForth, but OFF in TurboForth.

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
14 hours ago, Hans23 said:

Hi,   is there a way to synchronize to VBLANK in TurboForth?  A counter would be useful, or maybe an efficient way to wait for the vertical blanking interrupt to occur.   Any hints would be greatly appreciated.  Thanks!   -Hans

 

Here is the ALC [corrected] to poll for the next VDP interrupt in TurboForth (WI , say), which must have the TMS9900 Assembler (block 9) loaded:

\ Wait for VDP Interrupt
ASM: WI  ( -- )
   R1 $8000 LI,            \ VDP interrupt mask
   $8802 @@ R2 MOVB,       \ reset VDP status byte
   BEGIN,                  
      $8802 @@ R2 MOVB,     \ get VDP status byte
      R1 R2 COC,           \ VDP interrupt?
   EQ UNTIL,               \ loop if not
;ASM

and, without the TMS9900 Assembler [corrected]:

\ Wait for VDP Interrupt
HEX
CODE: WI  ( -- )
   0201 8000 D0A0 8802 D0A0 8802 2081 16FC 
;CODE

...lee

Edited by Lee Stewart
corrections
  • Like 3

Share this post


Link to post
Share on other sites

I just tried polling 837B and I never see a change on bit 0 in TF or Camel Forth (interrupts mostly on). I am missing something from Tursi's post clearly.

 

I tried do Lee's code in Forth just for fun and the loops are too slow to reliably catch the vertical blanking interrupt every time.

 

I did this little test in Camel99 Forth and it seems to look cleaner when I wait  "NE UNTIL".

I think because I am masking out the bit versus using the COC instruction. 

NEEDS .S   FROM DSK1.LOWTOOLS

MARKER /REMOVE

CODE WAITVDP ( -- ) 
   8802 @@ R0 MOVB,
   BEGIN,
     8802 @@ R0 MOVB,
     R0 8000 ANDI,
   NE UNTIL,   
   NEXT,
ENDCODE      

: FILLA  0 3C0 41 VFILL ;

: TEST
    BEGIN 
      WAITVDP
      FILLA  
      WAITVDP
      PAGE  
      ?TERMINAL 
    UNTIL ;

 

 

 

 

 

  • Like 1

Share this post


Link to post
Share on other sites

I had some unexpected behavior when calling WI and COINC in a loop - Sometimes, things seem to work fine, but if I changed the invocation order in the loop, COINC failed to detect overlaps.  It seems like TurboForth is somehow dealing with interrupts, after all:

; COINC ( tolerance spr1 spr2 -- flag )
_coinc  mov @bank1_,@retbnk         ; return to bank 1 if taking interrupt
        limi 2                      ; let interrupts run 
        limi 0
        mov *stack+,r2              ; get spr2
        mov *stack+,r1              ; get spr1
        movb @>837b,r0              ; get vdp status register (ed/as pg. 405)
        andi r0,>2000               ; check collision bits
        jeq miss                    ; if NO sprites are in collision then
                                    ; don't do the rest of the test.

If I understand correctly, the LIMI 2/LIMI 0 sequence unmasks interrupts for the duration of one instruction, is that correct?  Does this cause queued (VDP) interrupts to be processed?  In that case, would WI interfere in clearing the VDP status word, which would then make COINC not detect the overlap?

 

I dug a little deeper into the TurboForth code and there seems to be an interrupt service routine.  I guess this would be a good place to hook in to, which would also allow me to maintain an incrementing frame counter.

 

Thank you for your help!

-Hans

Share this post


Link to post
Share on other sites

I am not an expert but I cannot think of a way that TI-99 can "queue" interrupts beyond a count of one. I think it is more the case of if an Interrupt is pending when the LIMI 2 runs the interrupt code will run. Then when it completes the LIMI 0 prevents another interrupt.

 

The problem I see with this method is that it turns the interrupt service code into a kind of polled activity that only runs when you allow it to run, versus something that runs on a digital timer with precision. So to my mind it defeats the purpose of using an interrupt routine.

 

I you wanted a polled service it is very simple to make a Forth word POLL for example and put in your code whenever you are looping and before low level I/O routines. This will accomplish the same thing but you have complete control of when your "background" code runs.

This is in fact how the traditional Forth multi-tasker works. The word PAUSE is used.

 

So hooking into the systems interrupt may not be ideal when TF is preventing the interrupt from happening. You will have to try it to see how frequently it runs.

 

I did a little video on hooking the console interrupt in Camel99 Forth that may be of use.

Most of the words should work in TF except for CODE ENDCODE which are ASM: ;ASM  in TF.

 

 

  • Like 1

Share this post


Link to post
Share on other sites
8 hours ago, Hans23 said:

It seems like TurboForth is somehow dealing with interrupts, after all:

 

I never said TurboForth did not deal with interrupts because, indeed, it does—as you discovered. What I said was that interrupts are normally off.

 

The “LIMI 2” instruction is only ever executed when followed immediately by “LIMI 0”. There are only 11 instances of this pair of instructions in the ALC of the TurboForth cartridge (I did not analyze utilities in blocks files). That said, these get called with virtually all keyboard/joystick activity and most accesses to the VDP so @Tursi’s comments are still germane.

 

...lee

Share this post


Link to post
Share on other sites

The TMS9900 has no interrupt queue. Interrupts are level-driven, not edge-driven. When there is a VDP interrupt, the VDP raises the interrupt line, this passes through the 9901 and then to the INTREQ input of the 9900.

 

The VDPINT stays asserted until you read the video status register. This is normally done in the interrupt handler in ROM. So if you mask away the interrupts with LIMI 0, and an interrupt occurs, nothing happens until you do a LIMI 2, in which case the still pending interrupt is sensed. However, if during the LIMI 0 phase the video status register is read, the interrupt is gone.

 

The 9995 is a bit different in that respect, as it latches incoming interrupts in the internal CRU space.

  • Like 1

Share this post


Link to post
Share on other sites

What I'm really after is a real-time counter.  I had, not knowing a lot about the TI-99/4A, assumed that the vertical blanking event would be a good way to get a 50/60Hz interrupt source.

 

From what I read, it seems that with TurboForth, it would be best to rely on the LIMI 2 LIMI 0 sequence for handling the interrupts, and make sure that one of the words that has this sequence is called often enough. I would think that installing a user-defined ISR handler in the USRISR hook would be the appropriate place to interface to the "polled interrupts" scheme.  Does that make sense?  Is vertical blanking the only interrupt source, or are there any other interrupts that could cause the USRISR to be invoked?


Sorry for the naive questions, I'm still new to the TI-99/4A and to Forth and I appreciate your patience!

Share this post


Link to post
Share on other sites
4 hours ago, TheBF said:

I did a little video on hooking the console interrupt in Camel99 Forth that may be of use.

Most of the words should work in TF except for CODE ENDCODE which are ASM: ;ASM  in TF.

This is useful, thanks!

  • Like 2

Share this post


Link to post
Share on other sites
25 minutes ago, Hans23 said:

What I'm really after is a real-time counter.  I had, not knowing a lot about the TI-99/4A, assumed that the vertical blanking event would be a good way to get a 50/60Hz interrupt source.

 

From what I read, it seems that with TurboForth, it would be best to rely on the LIMI 2 LIMI 0 sequence for handling the interrupts, and make sure that one of the words that has this sequence is called often enough. I would think that installing a user-defined ISR handler in the USRISR hook would be the appropriate place to interface to the "polled interrupts" scheme.  Does that make sense?  Is vertical blanking the only interrupt source, or are there any other interrupts that could cause the USRISR to be invoked?


Sorry for the naive questions, I'm still new to the TI-99/4A and to Forth and I appreciate your patience!

 

The TurboForth ISR is called through >83C4 every time the console ISR runs and the user’s ISR is called through USRISR (>A008) every time the TurboForth ISR runs.

 

Others here can give you better details, but other interrupts can occur in addition to the VDP interrupt—notably, peripheral ROMs, etc., are polled for interrupts.

 

You can use the VDP interrupt timer at >8379, which is ticked every time the VDP interrupt is serviced. As a one-byte timer, it rolls over to 0 every 256 ticks (60ths of a second).

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
3 minutes ago, Lee Stewart said:

You can use the VDP interrupt timer at >8379, which is ticked every time the VDP interrupt is serviced. As a one-byte timer, it rolls over to 0 every 256 ticks (60ths of a second).

Lee, this looks quite appropriate, thanks!

  • Like 1

Share this post


Link to post
Share on other sites
9 minutes ago, Lee Stewart said:

 

The TurboForth ISR is called through >83C4 every time the console ISR runs and the user’s ISR is called through USRISR (>A008) every time the TurboForth ISR runs.

 

Others here can give you better details, but other interrupts can occur in addition to the VDP interrupt—notably, peripheral ROMs, etc., are polled for interrupts.

 

You can use the VDP interrupt timer at >8379, which is ticked every time the VDP interrupt is serviced. As a one-byte timer, it rolls over to 0 every 256 ticks (60ths of a second).

 

...lee

Is the accuracy affected with interrupts disabled most of the time?

  • Like 1

Share this post


Link to post
Share on other sites
15 minutes ago, TheBF said:

Is the accuracy affected with interrupts disabled most of the time?

 

All keyboard/joystick accesses and almost all accesses to the VDP start with

       LIMI 2
       LIMI 0

I say, “almost all” because there are instances where that is not the case—notably, the word VMBR does not enable interrupts whereas [email protected] V! and VMBW all do.

 

Other than that, I cannot say. Hopefully, @Willsy gets in here ere long.

 

...lee

  • Like 3

Share this post


Link to post
Share on other sites
3 minutes ago, mizapf said:

You can also set up a timer with the 9901 (timer mode).

Excellent point.  I use it for the ISO Forth word MS which delays n milli-seconds.

 

My code is not TurboForth compatible because I keep top of stack cached in R4 but here it is to give you some ideas.

This is in cross-compiler Forth so it has some magic words included that you won't need. Happy to answer questions.

 

Spoiler
\ TICKTOCK.HSF  TMS9901 hardware timer interface for Camel 99 Forth

\ credit to: http://www.unige.ch/medecine/nouspikel/ti99/tms9901.htm#Timer

\ timer resolution:  64 clock periods, thus 64*333 = 21.3 microseconds
\ Max Duration    :  ($3FFF) 16383 *64*333 ns = 349.2 milliseconds

CROSS-ASSEMBLING
CODE: TMR!   (  -- )         \ load TMS9901 timer to max value 3FFF
             W 3FFF LI,      \ load scratch register W with MAXIMUM timer value
             R12 CLR,        \ CRU addr of TMS9901 = 0
             0 LIMI,
             0   SBO,        \ SET bit 0 to 1, Enter timer mode
             R12 INCT,       \ CRU Address of bit 1 = 2 , I'm not kidding
             W 0E LDCR,      \ Load 14 BITs from R1 into timer
             R12  DECT,      \ go back to address 0
             0    SBZ,       \ reset bit 0, Exits clock mode, starts decrementer
             2 LIMI,
             NEXT,           \ 16 bytes
             END-CODE

CODE: [email protected]   ( -- n)         \ read the TMS9901 timer
             TOS PUSH,
             R12 CLR,
             0 LIMI,
             0 SBO,          \ SET bit 0 TO 1, ie: Enter timer mode
             TOS 0F STCR,    \ READ TIMER (14 bits plus mode bit) into W
             TOS  1 SRL,     \ Get rid of mode bit
             0 SBZ,          \ SET bit 1 to zero
             2 LIMI,
             NEXT,
             END-CODE

CODE: |-| ( x y -- x n )    \ : |-|   OVER - ABS ;
            *SP TOS SUB,
             TOS    ABS,    \ get ABS of subtraction
             NEXT,
             END-CODE

TARGET-COMPILING
[CC] DECIMAL [TC]

\ 1 JIFF = 1/60 second. (16.6666 mS)
\ 650 is tested number using Classic99 emulator.
\ Real IRON might need tweeking
: JIFF     ( -- )   [email protected]  BEGIN   PAUSE [email protected] |-| 650 > UNTIL  DROP ;

\ Because JIFFS are 1/60 seconds, there is a lot of time while PAUSE 
\ switches to all the tasks. So JIFFS are the preferred way to delay
\ in a multi-tasking program.
: JIFFS    ( n -- ) 0 ?DO JIFF LOOP ;

: MS  ( n -- )  4 RSHIFT  JIFFS ;  \ MS/16= JIFFS


[CC] HEX [TC]

 

 

Share this post


Link to post
Share on other sites
On 12/20/2020 at 12:45 PM, Lee Stewart said:

 

Interrupts are normally ON in TI Forth and fbForth, but OFF in TurboForth.

 

...lee

Are you sure? I booted Turboforth when I wrote that email and it breakpointed in the interrupt routine while just sitting at the command prompt...

 

(Edit: ah, I see your explanation in post 11 -- I guess we now have system that try to leave interrupts on all the time, but 99% of the time LIMI 2/LIMI 0 is the very TI definition of interrupts being enabled ;) )

 

Edited by Tursi
  • Like 2

Share this post


Link to post
Share on other sites

Interrupts are off most of the time in TurboForth. I legacy of a naive design decision by yours truly. At the time though, I was going for speed.

 

I seem to remember experimenting with getting TF with interrupts enabled but couldn't get it to work. At least not easily, so I gave up!

 

It's fairly easy to install a user ISR in TF. Poke in some machine code, either using the assembler or using CODE: load the ISR vector at >A008 with the address of the machine code.

 

Caveats:

  • The ISR vector must point to the machine code, not the CFA of the machine code as built by the Forth assembler or by CODE: This amounts to ticking the machine code word and adding 2 to move past the CFA
  • The ISR must use RT/B *R11 instruction to return to the TF (TF does a regular BL to your ISR)
  • Since TF does a regular BL to your ISR, you are in TF's register workspace, so be careful! Don't change R3, R4, R5 and R12 - or, if you change them, restore them before you return.

Example to create a 'frame counter' ISR:

 

variable fcount

ASM: ISR
  fcount @@ inc,
  r11 ** b,
;ASM

' ISR 2+ $A008 !

(at this point the ISR is loaded and running)

FCOUNT @ .
FCOUNT @ .

Here's a video I did last night showing the process for those that are interested 🙂

 

 

 

  • Like 3
  • Thanks 2

Share this post


Link to post
Share on other sites

Lol, I didn't see that B**** word entered. Hmm..I've watched twice....

But kiddn' aside, interesting.

Merry Christmas Mark

  • Like 1
  • Haha 1

Share this post


Link to post
Share on other sites
On 12/21/2020 at 6:26 AM, TheBF said:

I am not an expert but I cannot think of a way that TI-99 can "queue" interrupts beyond a count of one. I think it is more the case of if an Interrupt is pending when the LIMI 2 runs the interrupt code will run. Then when it completes the LIMI 0 prevents another interrupt.

 

The problem I see with this method is that it turns the interrupt service code into a kind of polled activity that only runs when you allow it to run, versus something that runs on a digital timer with precision. So to my mind it defeats the purpose of using an interrupt routine.

 

I you wanted a polled service it is very simple to make a Forth word POLL for example and put in your code whenever you are looping and before low level I/O routines. This will accomplish the same thing but you have complete control of when your "background" code runs.

This is in fact how the traditional Forth multi-tasker works. The word PAUSE is used.

 

So hooking into the systems interrupt may not be ideal when TF is preventing the interrupt from happening. You will have to try it to see how frequently it runs.

 

I did a little video on hooking the console interrupt in Camel99 Forth that may be of use.

Most of the words should work in TF except for CODE ENDCODE which are ASM: ;ASM  in TF.

 

 

Hmm since RXB even before 2001 versions had CALL ISRON(variable) or CALL ISROFF(variable)

CALL ISROFF(variable) put the ISR hook value into that variable and stopped any ISR interrupts like Assembly ISR hooks.

CALL ISRON(variable) put the Assembly ISR hook into variable and turned ISR interrupts like Assembly back on.

Reason for this in RXB was so you could stop programs that had a interrupt and save or load them then restart them.

This was demoed to show 23 Assembly programs all run from DISK from same XB program in Demo.

Later did that same demo using SAMS memory. Always cool how I see rediscoveries of this stuff later.

 

  • Like 2

Share this post


Link to post
Share on other sites

I am rediscovering everything.   :) 

I have a listing here in a binder where I hooked the ISR in TI-Forth dated Sept 17, 1987.

Did I remember any of it after 30 years. Not really.

  • Like 2

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