Tali Forth for the 65c02

Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers.
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

Hi @adrianhudson, I'm happy to hear you are programming in Forth. While Tali doesn't have an RDROP word (you could write one!), it does have R> and DROP which will transfer data from the return stack to the data stack, and then discard it. This can be used to return to the word before the word that called this one with some caveats:

Tali's native compiler will get in your way. For this to work properly, you need each word to be compiled as a JSR. To make that happen, you can either:

set nc-limit to zero like so:

Code: Select all

0 nc-limit !
This has the downside of compiling ALL words as JSRs, and that's not as fast as inlining them when they are small.

or you can simply mark the words in question with never-native. This goes just after you finish defining a new word, and it marks that word in the dictionary to always be compiled as a JSR (and never "native", which is Tali's term for when it inlines the assembly code instead of using a JSR). Here's a simple example of a word returning to a grandparent word:

Code: Select all

: child ." going back to grandparent" cr
        r> drop ; never-native
: parent child ." This shouldn't print" cr ; never-native
: grandparent ." Starting main word.. " cr
              parent
              ." Back in main word." cr ;
and an example of running this code:

Code: Select all

grandparent Starting main word.. 
going back to grandparent
Back in main word.
 ok
You can use see to investigate each word, eg. see parent gives:

Code: Select all

nt: 83E  xt: 848  header: 40 06 00 24 
flags: HC 0 NN 1 AN 0 IM 0 CO 0 DC 0 LC 0 FP 0 | UF 0 ST 0 
size (decimal): 36 

0848  20 0A 08 20 3B A2 14 00  54 68 69 73 20 73 68 6F   .. ;... This sho
0858  75 6C 64 6E 27 74 20 70  72 69 6E 74 20 A8 94 A9  uldn't p rint ...
0868  0A 20 54 86                                       . T.

848    80A jsr     child
84B   A23B jsr     SLITERAL 14 This shouldn't ...
864   94A8 jsr     type
867      A lda.#
869   8654 jsr     
 ok
Note the NN flag is set here - that makes Tali always compile this word as a JSR, so it's possible to mess with the return addresses on the return stack. You can also see the 80A jsr is a jsr to the child word - it's compiled as a jsr because that word is also marked never-native.
If you're wondering about the lda.# and unnamed JSR in there, that's the CR word, which loads $A (the CR character) into the A register and calls a function (which does not have a Forth name) to print it out.
adrianhudson
Posts: 169
Joined: 30 Apr 2022
Location: Devon. UK
Contact:

Re: Tali Forth for the 65c02

Post by adrianhudson »

@SamCoVT Thank you VERY much for your help! I like Tali very much indeed. It is the resident OS on my little homebrew 6502. I have just finished converting an assembler program to FORTH that I wrote some time ago that decodes the UKs MSF time signal and sets the system clock. Its quite timing dependant and I am impressed at the speed of FORTH.
The reason for this need for something to emulate THROW and CATCH came about whilst converting this program. As others have pointed out there are ways of returning from previous levels of code using return codes but I just wanted something cleaner.
I will implement your solution and let you know how I get on. I need to learn/understand more about the xxx-native words - here is my opportunity.
adrianhudson
Posts: 169
Joined: 30 Apr 2022
Location: Devon. UK
Contact:

Re: Tali Forth for the 65c02

Post by adrianhudson »

This works very nicely Thanks SamCoVT
I made a little word "levels-back" that drops its own return and the number you tell it.

: levels-back r> drop 0 do r> drop loop ;

: L3 cr ." in level three" 2 levels-back cr ." still in level three" ; never-native
: L2 cr ." in level two" L3 cr ." back in level two" ; never-native
: L1 cr ." in level one" L2 cr ." back in level one" ;
: L0 cr l1 ;
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Tali Forth for the 65c02

Post by barrym95838 »

adrianhudson wrote:

Code: Select all

: levels-back r> drop 0 do r> drop loop ;
I think that code is very, very non-portable, because many (pretty much all?) vintage FORTHs keep the loop index and limit on the return stack and would choke on this. The allegation that Tali doesn't choke is mildly impressive.
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

Hi @adrianhudson, that looks pretty good. As Mike B. points out, the fact that R> works inside of a loop is very Tali specific because Tali puts its loop data (eg. which # loop it is currently on and when it should stop) in a different place, while many older Forths use the return stack. Technically, R> is usually not allowed inside of a loop for that reason, but it will work on Tali specifically (as long as you have a recent version - this was changed about a year ago to the version where you can do this).

Do note that when you drop the first return address, you will already be going one level back (eg. to wherever the semicolon from L3 would take you in your example) when you return from levels-back.

If it's working exactly as you expect, then you're fine. If it's going back one more level than you expect, I'll recommend the following change:

Code: Select all

: levels-back 0 ?do  r> drop  loop ;  never-native
The only difference is that ?do will run the loop zero times if you pass zero (whereas do will run 65536 times if you give it the same starting and ending number), and it drops one less return stack address. Now, calling 2 levels-back in L3 returns to L1 (instead of L0). Here's my output with the modified levels-back:

Code: Select all

in level one
in level two
in level three
back in level one ok
On my to-do list for the manual for Tali is writing a tutorial for native compiling. There is a section in the manual on native compiling, but it's not in the tutorial style (with examples to type in and follow along). I'd love to see what you come up with for a solution to your problem as it might be a good example to go over in the tutorial when I get to writing it.

One major change that has happened recently (and isn't in the manual yet) is that Tali now decides when words can/can't be native compiled based on whether there are jumps or branches in the word. Previously (and the documentation still claims this), Tali would mark all user created words as never-native because it couldn't tell if they could be inlined or not. Now it watches as the word is assembled and will only flag a word as never native when there are flow control words (eg. loops, ifs, or case statements) in the word being compiled.

Your levels-back word, for example, will be flagged never-native because it has a do loop in it. It would still be good to add the never-native keyword after it anyway, in case Tali's native compiling gets better and allows short loops. If you use see on a word (eg. see levels-back) you can pay attention to the NN (never native) flag at the top to see what Tali has decided, and the never-native word can be used to force the most recent word to always have the NN flag set and therefore be compiled as a JSR.

You're welcome to start a new thread here for your project you are working on. I'm sure there are several of us here that would be interested in your progress and to see what you come up with for a solution.
adrianhudson
Posts: 169
Joined: 30 Apr 2022
Location: Devon. UK
Contact:

Re: Tali Forth for the 65c02

Post by adrianhudson »

barrym95838 wrote:
adrianhudson wrote:

Code: Select all

: levels-back r> drop 0 do r> drop loop ;
I think that code is very, very non-portable, because many (pretty much all?) vintage FORTHs keep the loop index and limit on the return stack and would choke on this. The allegation that Tali doesn't choke is mildly impressive.
You must remember I am completey FORTH naive! I try things... if they work I am happy. Possibly I shouldn't be but I do not know what I do not know! :-) (Although, thank you, I do now know!)
adrianhudson
Posts: 169
Joined: 30 Apr 2022
Location: Devon. UK
Contact:

Re: Tali Forth for the 65c02

Post by adrianhudson »

SamCoVT wrote:
Hi @adrianhudson, that looks pretty good.
You are very kind. I know I don't deserve it. lol

A far as the rest of your comments go - thank you for them. I will amend my code accordingly.

I have just been messing with this native/non native stuff. I defined three simple words, all identical except for the name and marked them allow-native, never-native and always native
: x 2 2 * . ; allow-native
: y 2 2 * . ; never-native
: z 2 2 * . ; always-native
I used see to display them expecting at least one of them to be different but apart from the flags they have all compiled identically. Am I misunderstanding something here? (Or am I going to embarrass mysef once again!?)
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

The native compiling flags only affect when you use that word in another word. After your x y and z definitions, try:
: newword x y z ;
see newword

You can also experiment with nc-limit - it's a variable you set to a # of bytes and any words smaller than that will be native compiled (if it's allowed for that word).

To expand on what Mike noted about R> inside of a do loop, here's the section (3.2.3.3 Return Stack) of the 2012 standard that discusses not using R> inside of loops:
https://forth-standard.org/standard/usa ... eturnstack

Items on the return stack shall consist of one or more cells. A system may use the return stack in an implementation-dependent manner during the compilation of definitions, during the execution of do-loops, and for storing run-time nesting information.

A program may use the return stack for temporary storage during the execution of a definition subject to the following restrictions:
  • A program shall not access values on the return stack (using R@, R>, 2R@, 2R> or NR>) that it did not place there using >R, 2>R or N>R;
  • A program shall not access from within a do-loop values placed on the return stack before the loop was entered;
  • All values placed on the return stack within a do-loop shall be removed before I, J, LOOP, +LOOP, UNLOOP, or LEAVE is executed;
  • All values placed on the return stack within a definition shall be removed before the definition is terminated or before EXIT is executed.
Forth, of course, lets you break the rules as long as you understand the ramifications. The above rules are for software that is reasonably guaranteed to run properly on all Forths that implement the standard. Because many Forths use the return stack to hold where they are in a do loop, the standard simply says not to mess with the return stack inside of loops. Tali currently implements for loops a different way from many other Forths (see the posts from May 2024 in this thread for some details) and has a separate loop stack. By default, this lives starting at $100 and growing up (so it uses the same memory as the return stack, but from the other end), but it can be moved to anywhere in the memory map.

All this means is that your code may not run on other forths, and that you'd need to rewrite your levels-back word for those forths. Because of what you are doing (skipping multiple return levels), I'd argue that the levels-back word would always need to be specific to the forth you are running it on.

[edited to fix typos]
Last edited by SamCoVT on Tue Mar 18, 2025 6:45 pm, edited 1 time in total.
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

adrianhudson wrote:
: x 2 2 * . ; allow-native
: y 2 2 * . ; never-native
: z 2 2 * . ; always-native
To follow up on this, the word . to print the result is 33 bytes long (see . to find that out). That's longer than the default settings of nc-limit (which is 20), so it's compiled as a JSR to the . word. If you set nc-limit larger than 33, for example:

40 nc-limit !

and then compile these words, you will find the source for . (which itself is a bunch of JSRs - it starts with a stack depth check to make sure there is at least one item on the stack if you've left underflow checking on (it is on by default)) in your X, Y, and Z words instead. Conversely, if you do:

0 nc-limit !

and then compile your x, y, and z words, then they will all be JSRs, including JSRs to the word 2, which is an actual word in Tali (small numbers in the dictionary compile faster, so 0, 1, and 2 are in the dictionary).

Code: Select all

\ With nc-limit set to 40
see x 
nt: 891  xt: 896  header: 00 01 64 39 
flags: HC 0 NN 0 AN 0 IM 0 CO 0 DC 0 LC 0 FP 0 | UF 0 ST 0 
size (decimal): 57 

0896  CA CA A9 02 95 00 74 01  CA CA A9 02 95 00 74 01  ......t. ......t.
08A6  20 42 CE 20 90 95 E8 E8  20 3D CE 20 2A 86 20 CC   B. ....  =. *. .
08B6  80 20 56 9E 20 4B 89 20  23 8C 20 C6 8E 20 3C 91  . V. K.  #. .. <.
08C6  20 01 8C 20 A8 94 20 BD  91                        .. .. . .

896        dex
897        dex
898      2 lda.#
89A      0 sta.zx
89C      1 stz.zx
89E        dex
89F        dex
8A0      2 lda.#
8A2      0 sta.zx
8A4      1 stz.zx
8A6   CE42 jsr     2 STACK DEPTH CHECK
8A9   9590 jsr     um*
8AC        inx
8AD        inx
8AE   CE3D jsr     1 STACK DEPTH CHECK
8B1   862A jsr     dup
8B4   80CC jsr     abs
8B7   9E56 jsr     0
8BA   894B jsr     <#
8BD   8C23 jsr     #s
8C0   8EC6 jsr     rot
8C3   913C jsr     sign
8C6   8C01 jsr     #>
8C9   94A8 jsr     type
8CC   91BD jsr     space
 ok

\ With nc-limit set to 0
see x 
nt: 8D0  xt: 8D5  header: 00 01 91 0C 
flags: HC 0 NN 0 AN 0 IM 0 CO 0 DC 0 LC 0 FP 0 | UF 0 ST 0 
size (decimal): 12 

08D5  20 1A 9E 20 1A 9E 20 DD  91 20 B7 85               .. .. . . ..

8D5   9E1A jsr     2
8D8   9E1A jsr     2
8DB   91DD jsr     *
8DE   85B7 jsr     .
 ok
One extra note - the word * is implemented as UM* DROP. UM* multiplies two 16-bit values with a 32-bit result, and the DROP gets rid of the high half so there's just a 16-bit result. That may explain the UM* and the inx inx (two inx instructions make up DROP) that you see in some spots for *
adrianhudson
Posts: 169
Joined: 30 Apr 2022
Location: Devon. UK
Contact:

Re: Tali Forth for the 65c02

Post by adrianhudson »

Sam thank you _very_ much. The penny has dropped at last. I don't know how long its taken me to get that into my thick skull but its been a long time!!
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

No problem. I couldn't remember how Tali decides if a compiled word can be native or not so I had to go look (Patrick wrote that decision making code for Tali, and Scot (the original author of Tali) wrote the original native compiling routine). If all the words inside aren't marked never-native, then the word being compiled will have the never-native bit cleared when ; runs at the very end of the word. I thought it was just the flow control words, but it's any never-native word that will cause a new word to also be marked never native (unless you change that with allow-native or always-native). The code that makes the final decision is in xt_semicolon in words/core.asm

Now that you understand how it works, I'd be happy to hear any recommendations or thoughts for a tutorial that would clearly show native compiling off to a new Tali user. I think walking through something like your X, Y, and Z words might be good.

[edited for grammar]
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Tali Forth for the 65c02

Post by SamCoVT »

leepivonka wrote:

Code: Select all

Define a word:
    : RDrop ( R: addr -- ) \ discard a return stack entry
      [ $68 c, $68 c,  \ pla; pla
      ] ; always-native compile-only
I missed this post originally when reading through the recent discussion, but I just wanted to point out that this RDrop word from leepivonka is a little faster than using R> and DROP. This version was hardcoded with the opcode bytes, which is fine, but you could also use Tali's assembler like so:

Code: Select all

assembler-wordlist >order
    : RDrop ( R: addr -- ) \ discard a return stack entry
      [ pla pla ] ; always-native compile-only
Turning on the compile-only flag is also smart. It will give a warning if you try to run it when you're not compiling a new word, rather than trashing the return stack.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Tali Forth for the 65c02

Post by JimBoyd »

As I mentioned previously my own Forth, as well as Scott Ballantyne's Blazin' Forth, also adjusts the limit and index of a DO LOOP to take advantage of a branch on overflow; however, Scott Ballantyne's Blazin' Forth adds $8000 to the limit and subtracts this adjusted limit from the index. The result is the adjusted index is the same as it would be for Tali Forth and the same number of loop iterations will happen before the overflow. The index word I now adds the adjusted index and adjusted limit to get the actual index. The advantage to this approach is the low byte of the limit stays the same so it does not need adjusted. This will save six bytes. My own Forth takes advantage of the fact that adding $8000 to the limit only changes the sign bit of the limit. My Forth replaces this:

Code: Select all

   .
   .
   .
   CLC
   LDA 3,X
   ADC #$80
   PHA
   STA 3,X
   LDA 2,X
   PHA
   .
   .
   .

With this:

Code: Select all

   .
   .
   .
   LDA 3,X
   EOR #$80
   PHA
   STA 3,X
   LDA 2,X
   PHA
   .
   .
   .

Which saves another byte and it works just fine.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Tali Forth for the 65c02

Post by JimBoyd »

barrym95838 wrote:
adrianhudson wrote:

Code: Select all

: levels-back r> drop 0 do r> drop loop ;
I think that code is very, very non-portable, because many (pretty much all?) vintage FORTHs keep the loop index and limit on the return stack and would choke on this. The allegation that Tali doesn't choke is mildly impressive.
This version of LEVELS-BACK should be portable. It works on my ITC Forth.

Code: Select all

: LEVELS-BACK  ( +N -- )
   1+
   BEGIN
      R> DROP
      1- ?DUP 0=
   UNTIL ;

Post Reply