Jump to content
IGNORED

TurboForth Assembler


Lee Stewart

Recommended Posts

I decided to move my explorations into TurboForth Assembler to a different thread (this one) rather than hijacking another thread. My post here (http://www.atariage....ost__p__2415039) alludes to my current project, viz., implementing Tursi's TMS9900-Assembler-coded 14-bit countdown timer (http://www.atariage....ost__p__2021341) using the TMS9901 Programmable Systems Interface and based on Thierry Nouspikel's code ("TMS9901" link at http://www.nouspikel...titechpages.htm).

 

My two Forth words have the same name as Tursi's TMS9900 Assembler code: INIT01 to start the timer and READ01 to get its current value. My TurboForth Assembler code follows:

 

ASM: INIT01	 ( --- )
  R12 R2 MOV,		   \ Save return address
  R12 CLR,		   \ CRU base of the TMS9901
  0 SBO,			   \ Enter timer mode
  R1 $3FFF LI,		   \ Maximum value
  R12 INCT,		   \ Address of bit 1
  R1 14 LDCR,		   \ Load value
  R12 DECT,
  0 SBZ,			   \ Exit clock mode, start decrementer
  R2 R12 MOV,		   \ Restore return address
;ASM

ASM: READ01	( --- n )
  R12 R2 MOV,		   \ Save return address
  R12 CLR,		   \ CRU base of the TMS9901
  0 SBO,			   \ Enter timer mode
  SP INCT,		   \ Make space on stack to leave timer value
  *SP 15 STCR,		   \ Read current value (plus mode bit) and put on stack
  *SP 1 SRL,		   \ Get rid of mode bit
  0 SBZ,			   \ Exit clock mode, decrementer continues
  R2 R12 MOV,		   \ Restore return address
;ASM

 

Both words seem to compile OK. INIT01 appears to work, but I cannot tell for sure because executing READ01 causes a stack underflow, which baffles me. Any ideas as to what is wrong?

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

Hmmm... that's weird. Looks fine to me. Did you single step it to make sure (using the classic 99 debugger)?

 

I'm wondering if the indirect store is legal on a store cru instruction?

 

In other words, rather than

 

STCR *Rx,y

 

Maybe it needs to be done in two steps...

 

STCR Rx,y

MOV Rx,*SP

 

I think that may be the source of your trouble. I'm guessing you can only store CRU bits directly to a register, thus your assembly instruction, as currently coded is producing an op-code that actually means something else.

 

Plausible?

 

Mark

Link to comment
Share on other sites

Lee,

 

I just got a chance to try your code. I was right and wrong at the same time! Right theory, wrong instruction! I should have taken the time to study your code properly, but in my defence I had just got out of bed and was extremely bleery eyed, and wasn't even wearing my glasses!

 

The problem is the SRL instruction:

 

*SP 1 SRL,			  \ Get rid of mode bit

 

You can't indirectly shift a value. You can only shift a value in a register, not a value pointed to by a register (a limitation of the 9900 instruction set). When I check the code with the debugger, the instruction is simply interpreted as SRL R4,1 - this has the effect of dividing the stack pointer (not the value on the top of the stack!) by two. The interpreter in TF runs DEPTH at the end of each line of input to deduce if the stack underflowed or not, and correctly deduced a (rather huge) stack underflow!

 

You need to get the value directly in a register, shift it, and write it to the stack.

 

So, you'd need to re-write it slightly, maybe like this:

 

ASM: READ01	 ( --- n )
  R12 R2 MOV,	   \ Save return address
  R12 CLR,		  \ CRU base of the TMS9901
  0 SBO,		    \ Enter timer mode
  R1 15 STCR,	   \ Read current value (plus mode bit) into r1
  R1 1 SRL,		 \ Get rid of mode bit
  SP INCT,		  \ make space in stack
  R1 *SP MOV,	   \ place value on stack
  0 SBZ,		    \ Exit clock mode, decrementer continues
  R2 R12 MOV,	   \ Restore return address
;ASM

 

This assembles and executes as expected. I used the following test harness:

 

: TEST INIT01 READ01 . ;

 

However, it always returns 16381 (using Classic99). This may simply be because of the interval between INIT01 and READ01 being the same each time TEST is executed? Not sure. I know zilch about the 9901, I'd have to study the data sheet for the chip before I could comment further. But anyway, that's the stack fault sorted ;)

 

Hope this helps.

 

Mark

Link to comment
Share on other sites

Yep. You're setting the countdown timer to >3FFF in INIT01 and my little TEST word is immediately calling READ01 and returning with 16381 (>3FFD), so, 2 'ticks' occurred "between" INIT01 and READ01. That's 666nS. This would be the time taken by NEXT (the inner interpreter right at the heart of TF) and the first three instructions of READ01 if I'm not mistaken (because that's when you get the sample from the 9901).

 

One unit on the decrementer represents 64 clock periods. Well, we had two clock units, so that's 128*333ns=42.624uS.

 

I think. :)

 

Interesting.

Link to comment
Share on other sites

And---here it is in TI Forth (maybe I should start another thread :twisted: ):

 

HEX		   ( Put Forth into hexadecimal mode)
CODE INIT01	 ( --- )
  C CLR,		   ( CRU base of the TMS9901)
  0 SBO,		   ( Enter timer mode)
  1 3FFF LI,		   ( Maximum value)
  C INCT,		   ( Address of bit 1)
  1 E LDCR,		   ( Load value)
  C DECT,
  0 SBZ,		   ( Exit clock mode, start decrementer)
NEXT,

CODE READ01	 ( --- n )
  C CLR,		   ( CRU base of the TMS9901)
  0 SBO,		   ( Enter timer mode)
  1 F STCR,		   ( Read current value [plus mode bit])
  1 1 SRL,		   ( Get rid of mode bit)
  SP DECT,		   ( Make space on stack to leave timer value)
  1 *SP MOV,		   ( Timer value to stack)
  0 SBZ,		   ( Exit clock mode, decrementer continues)
NEXT,
DECIMAL		   ( Back to decimal mode)

 

The differences are as follows:

  • No need to save/restore R12 because TI Forth expects it to be used for the CRU.
  • Registers that do not have special names (SP, RP, etc.) are referenced by number only (I may change this in my copy of TI Forth! I have no idea why the TI programmers didn't do it because TMS9900 Assembler certainly has it that way.)
  • Used HEX before definitions because there is no explicit entry of hexadecimal numbers in TI Forth as there is in TurboForth (with a '$' prefix). This required all numbers, including register numbers, to be expressed in hexadecimal notation.
  • SP (stack pointer) is decremented (SP DECT,) in TI Forth to make space because the stack grows downward from high memory towards HERE .
  • CODE and NEXT, in TI Forth are pretty much equivalent to ASM: and ;ASM of TurboForth. Personally, I like the balance of the TurboForth words much better!

This code works quite the same as the TurboForth code above with Willsy's modifications in READ01 . Each tick of the timer is 23.33 µs 21.33 µs. The cycle time of the timer is 349.5 ms to complete 16384 ticks (16383 - 0, 3FFFh - 0). As Tursi indicated in my reference somewhere above, the timer can be started with a different maximum to cause it to cycle faster. For example, using 3FFh as the maximum value would start a timer with 1024 ticks and a cycle time of about 24 ms. Similarly, FFh as the maximum value would provide 256 ticks and a cycle time of about 6 ms. Perhaps I will redefine INIT01 to take a value from the stack for the timer's maximum value. The word should probably mask off the leftmost two bits to insure the value is no more than 14 bits wide, though, perhaps they are ignored. I will check.

 

...lee

Edited by Lee Stewart
  • Like 1
Link to comment
Share on other sites

A word of caution derived from a bad experience. While using the 9901 timer as a period clock I burnt out 2 9901's by entering and leaving the clock mode too fast. I was checking and rechecking instantly for a value to occur so I don't think your code will cause issues but maybe a note of caution here......

 

I

Link to comment
Share on other sites

A word of caution derived from a bad experience. While using the 9901 timer as a period clock I burnt out 2 9901's by entering and leaving the clock mode too fast. I was checking and rechecking instantly for a value to occur so I don't think your code will cause issues but maybe a note of caution here......

 

I

 

Marc...

 

Wow! Good to know. Is the 9901 located in a not-very-well-ventilated spot in the console or just vulnerable to heavy activity? Do you have any idea whether the maximum cycle time is energetically easier on the chip than shorter cycle times? Or, do you think it wise to employ a STOP01 word or to otherwise use it only intermittently?

 

...lee

Link to comment
Share on other sites

A word of caution derived from a bad experience. While using the 9901 timer as a period clock I burnt out 2 9901's by entering and leaving the clock mode too fast. I was checking and rechecking instantly for a value to occur so I don't think your code will cause issues but maybe a note of caution here......

 

I

 

Marc...

 

Wow! Good to know. Is the 9901 located in a not-very-well-ventilated spot in the console or just vulnerable to heavy activity? Do you have any idea whether the maximum cycle time is energetically easier on the chip than shorter cycle times? Or, do you think it wise to employ a STOP01 word or to otherwise use it only intermittently?

 

...lee

 

I don't think it's a heat issue and I also don't know what a stop01 word is. I have a feeling it is an issue of mode changing and reading bits to fast. It would be nice to know exactly how long the 9901 takes to change modes and the exact effect of writing data to output pins.

 

I don't have the technical data Lee only the bad experience. I think it stemmed from reading the clock, writing it back and starting the clock over and over in a tight loop.

Link to comment
Share on other sites

I don't think it's a heat issue and I also don't know what a stop01 word is. I have a feeling it is an issue of mode changing and reading bits to fast. It would be nice to know exactly how long the 9901 takes to change modes and the exact effect of writing data to output pins.

 

I don't have the technical data Lee only the bad experience. I think it stemmed from reading the clock, writing it back and starting the clock over and over in a tight loop.

 

Sorry. A "word" in this context is a Forth function/routine and I was just suggesting a name (STOP01) without explanation (my bad) for a function I might write that would stop the timer. But, if your intuition is right, that would probably not be necessary. Thanks for your input.

 

...lee

Link to comment
Share on other sites

...

 

Perhaps I will redefine INIT01 to take a value from the stack for the timer's maximum value. The word should probably mask off the leftmost two bits to insure the value is no more than 14 bits wide, though, perhaps they are ignored. I will check.

 

...lee

 

There would be no need to mask the input of the maximum value for the counter because the TI Forth line, 1 E LDCR, , ( 1 14 LDCR, in decimal mode and R1 14 LDCR, in TurboForth) will only load the low-order 14 bits, effectively masking off the high-order 2 bits.

 

...lee

  • Like 1
Link to comment
Share on other sites

OK...Here's a version of INIT01 that takes the maximum value for the counter from the stack:

 

TurboForth---

ASM: INIT01	 ( n --- )
  R12 R2 MOV,		   \ Save return address
  R12 CLR,		   \ CRU base of the TMS9901
  0 SBO,		   \ Enter timer mode
  *SP R1 MOV,		   \ Maximum value from stack to R1
  SP DECT,		   \ Decrement stack pointer
  R12 INCT,		   \ Address of bit 1
  R1 14 LDCR,		   \ Load value
  R12 DECT,
  0 SBZ,		   \ Exit clock mode, start decrementer
  R2 R12 MOV,		   \ Restore return address
;ASM

 

TI Forth---

HEX		   ( Put Forth into hexadecimal mode)
CODE INIT01	 ( n --- )
  C CLR,		   ( CRU base of the TMS9901)
  0 SBO,		   ( Enter timer mode)
  *SP+ R1 MOV,		   ( Max val: stack to R1; reduce stack)
  C INCT,		   ( Address of bit 1)
  1 E LDCR,		   ( Load value)
  C DECT,
  0 SBZ,		   ( Exit clock mode, start decrementer)
NEXT,
DECIMAL		   ( Back to decimal mode)

 

Notice in the TI Forth code that *SP+ R1 MOV, both copies the stack value and auto-increments the stack pointer (reducing stack size in TI Forth), thus obviating the necessity for a separate SP INCT, line ;-).

 

...lee

Link to comment
Share on other sites

  • 1 month later...

I don't remember the details, but it's possible to program the TMS 9901 in such a way that you destroy it in the 99/4A. That has to do with the fact that the chip has pins that can be either inputs or outputs on the same pin, and as written above, connecting outputs to each other isn't a good idea.

I've had to replace my 9901 in my 99/4A too, although not due to a programming mistake. Mine is now socketed.

Link to comment
Share on other sites

  • 5 years later...

I took a different approach to the 9901 timer and always initialize it to maximum value, then read it later to compute a duration.
Here is the code written in my DOS cross-compiler written in HsForth 2012 (my other hobby).

 

The TOS is a register that holds the top of Forth stack cached.

 

*Found a bug in this after I got my Camel Forth kernel running. TMR@ was not restarting the timer! No interrupts after 1st use.

Working verison is here now. Still not happy with MS. Not reliable. Probably need to code it in ASM too.

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   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
             NEXT,           \ 16 bytes
             END-CODE

CODE: TMR@   ( -- n)         \ read the TMS9901 timer
             R12 CLR,
             0 SBO,          \ SET bit 0 TO 1, ie: Enter timer mode
             W 0F STCR,      \ READ TIMER (14 bits plus mode bit) into W
             W 1 SRL,        \ Get rid of mode bit
             0 SBZ,          \ SET bit 1 to zero

             TOS PUSH,       \ make space for result
             TOS 3FFF LI,    \ load TOS with max timer value
             W TOS SUB,      \ subtract W from max timer value to get the duration
             NEXT,           \ 18 bytes
             END-CODE

\ these should be 21.3 uS ticks. MAX=3FFF
: TICKS     ( n -- ) TMR! BEGIN  PAUSE TMR@ OVER > UNTIL DROP ;

: MS   ( n -- )  2E * TICKS ;  ( MAX VALUE for n = 248 mS [$F8] )

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Thanks! Its great to be here.

 

I worked in Forth shop back in the 90s and before that I was inventing projects for myself while I was a broadcast engineer at a TV station up in Canada. Still have the bug. I know Willsy here from conversations on comp.lang.forth and some email correspondence a few years back. When I get this !#@$%! cross-compiler to build a working Forth Kernel I will hit you guys up for some info the TI-99 File system. I am build a Camel Forth and want to add basic ANS File I/O words so it can extend itself.

 

I am very impressed with the work I see you have done on TI-Forth. I cut my Forth teeth on that one But after getting a PC I left it alone for.

 

BF

Link to comment
Share on other sites

Thanks for your kind words.

 

After cooling my heels for a couple of months, I am currently back to updating the fbForth 2.0 Manual to incorporate the Addendum I published ~7 months ago as well as the new words I put in the kernel when I released the fbForth 2.0:9 cartridge binary ~4 months ago.

 

...lee

  • Like 2
Link to comment
Share on other sites

  • 3 weeks later...

I was having a heck of time with reliability reading the timer in a loop.

I noticed on the Classic99 debugger that the machine would crash and end up in the GPL workspace.

That seemed weird since I was not doing that so...

I masked off interrupts when entering the timer read code and restored them when finished and it works now.

CODE: TMR@   ( -- n)         \ read the TMS9901 timer
             0 LIMI,
             TOS PUSH,
             R12 CLR,
             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

\ these should be 21.3 uS ticks. maximum reliable value is 3FF0 due to Forth loop speed
: TICKS     ( n -- ) 3FFF SWAP - TMR! BEGIN  DUP TMR@ >  UNTIL DROP ;

So now that it works but the math does not seem to be working to get real time values.

For example:

 

333,000uS / 21.3uS = 15633 ticks (>3D11)

 

Should give a 333mS delay right?

 

But on Classic 99 this code run in a loop that counts to 10, runs in 12.9 seconds

 

: 333MS ( -- ) 3D11 TICKS ;

 

I have to use : 333MS ( -- ) 30C7 TICKS ; to make it come close to 10 secs.

 

Could be Forth loop overhead??

 

 

BF

Link to comment
Share on other sites

That seems the only conclusion, but that appears excessive. I am guessing it was 30 calls to 333MS for counting 10 seconds, which would be roughly 100 ms lost per call. That is a lot of time lost!

 

Maybe you could time the process without waiting for the timer by DROPping the > result and putting a '1' before UNTIL to see if that is indeed where the lag is.

 

...lee

Link to comment
Share on other sites

I don't guarantee the 9901 timer is strictly accurate - I've not run any tests against it except way back in the day with Sometimes' Sudoku, and by observation determining that it seemed to be ticking off seconds roughly once per second. ;) Do some controlled measurements to make sure the emulator is correct or just double-check your results against another one before trusting it.

Link to comment
Share on other sites

Thanks for the advice. I will dig in a little more.

 

I have 9919 driver that uses the Forth word mS coded like this.

 

: MS ( n -- ) 015C MIN 02F * TICKS ; \ max delay= DECIMAL 348 mS

 

And it seems correct when I did this:

 

: BEEP ( -- ) GEN1 1398 Hz -4 dB 170 MS MUTE ;
: HONK ( -- ) GEN1 218 Hz 0 dB 170 MS MUTE ;

 

So 1 call to TICKS seems pretty accurate meaning beep and honk seem to have the

right sound and I believe the System code uses 170 milliseconds for their duration.

 

Ah well a coder's work is never done.

 

BF

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