Jump to content
Sign in to follow this  
Lee Stewart

TurboForth Assembler

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 ([url="http://www.atariage.com/forums/topic/190287-turboforth-random-number-generator/page__view__findpost__p__2415039"]http://www.atariage....ost__p__2415039[/url]) alludes to my current project, [i]viz.[/i], implementing Tursi's TMS9900-Assembler-coded 14-bit countdown timer ([url="http://www.atariage.com/forums/topic/163646-not-prime/page__view__findpost__p__2021341"]http://www.atariage....ost__p__2021341[/url]) using the TMS9901 Programmable Systems Interface and based on Thierry Nouspikel's code ("TMS9901" link at [url="http://www.nouspikel.com/ti99/titechpages.htm"]http://www.nouspikel...titechpages.htm[/url]).

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

[CODE]
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
[/CODE]

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

...lee Edited by Lee Stewart

Share this post


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

Share this post


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

[code]*SP 1 SRL, \ Get rid of mode bit[/code]

You can't [i]indirectly [/i]shift a value. You can only shift a value [i]in [/i]a register, not a value [i]pointed to[/i] 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 [i]pointer [/i](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:

[code]
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
[/code]

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

[code]: TEST INIT01 READ01 . ;[/code]

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

Share this post


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

Share this post


Link to post
Share on other sites
And---here it is in TI Forth (maybe I should start another thread :twisted: ):

[CODE]
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)
[/CODE]

The differences are as follows:[list]
[*]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 [b]HEX [/b]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.
[*][b]SP[/b] (stack pointer) is [i]decremented[/i] ([b]SP DECT,[/b]) in TI Forth to make space because the stack grows downward from high memory towards [b]HERE [/b].
[*][b]CODE [/b]and [b]NEXT,[/b] in TI Forth are pretty much equivalent to [b]ASM:[/b] and [b];ASM[/b] of TurboForth. Personally, I like the balance of the TurboForth words much better!
[/list]
This code works quite the same as the TurboForth code above with Willsy's modifications in [b]READ01[/b] . Each tick of the timer is [s]23.33 µs[/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 [b]INIT01 [/b]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

Share this post


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

Share this post


Link to post
Share on other sites
[quote name='marc.hull' timestamp='1323216419' post='2419679']
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
[/quote]

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 [b]STOP01 [/b]word or to otherwise use it only intermittently?

...lee

Share this post


Link to post
Share on other sites
[quote name='Lee Stewart' timestamp='1323220506' post='2419710']
[quote name='marc.hull' timestamp='1323216419' post='2419679']
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
[/quote]

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 [b]STOP01 [/b]word or to otherwise use it only intermittently?

...lee
[/quote]

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.

Share this post


Link to post
Share on other sites
[quote name='marc.hull' timestamp='1323227627' post='2419755']
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.
[/quote]

Sorry. A "word" in this context is a Forth function/routine and I was just suggesting a name ([b]STOP01[/b]) 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

Share this post


Link to post
Share on other sites
[quote name='Lee Stewart' timestamp='1323211491' post='2419636']
...

Perhaps I will redefine [b]INIT01 [/b]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
[/quote]

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

...lee

Share this post


Link to post
Share on other sites
OK...Here's a version of [b]INIT01[/b] that takes the maximum value for the counter from the stack:

TurboForth---
[CODE]
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
[/CODE]

TI Forth---
[CODE]
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)
[/CODE]

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

...lee

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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

Share this post


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

Share this post


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

Share this post


Link to post
Share on other sites

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

Share this post


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

Share this post


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

Share this post


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

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...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...