Page 2 of 3

Re: 6502 TaliForth2 Implementation Questions

Posted: Tue Feb 25, 2025 2:03 pm
by SamCoVT
SparkyNZ wrote:
I'm not sure whether I should be handling line feed/CR myself or not but I'll come back to that tomorrow or the next day :-)
Tali can emit CR or LF or CR/LF on line endings. The default is LF. You can set that in your platform file (the other options are commented out):

Code: Select all

; define what the CR word should emit to kernel_putc at the end of each line

TALI_OPTION_CR_EOL := [ "lf" ]
;TALI_OPTION_CR_EOL := [ "cr" ]
;TALI_OPTION_CR_EOL := [ "cr", "lf" ]
Looking at your screenshot, it looks like you are already properly handling line endings.

Re: 6502 TaliForth2 Implementation Questions

Posted: Tue Feb 25, 2025 5:50 pm
by SparkyNZ
SamCoVT wrote:
That looks great. The default behavior of BYE is to run a BRK instruction, and the default ISR vector for BRK instructions (or any interrupts, really) is kernel_init, so the fact it starts over again is expected behavior. You can write a new kernel_bye if you have an OS or monitor you would like to return to, but lots of Tali users just use Tali as their OS. It has an interactive shell, a built in assembler (hidden by default - see the docs for how to enable it), and direct access to the hardware.

How familiar with Forth are you?
Thanks for the image tip - I'll give that a shot when I'm able to show something a bit more productive :)

I'm not very familiar with Forth at all. I dabbled a bit maybe a year or two ago but that was all - dabbling. I bought a bunch of Forth books (including Starting Forth) when I bought White Lightning but I was thinking it would be left there for my retirement (at least a decade away lol).

BTW I love that PDF manual you guys have put together! I'll definitely have to print myself a copy of that and get it ring-bound.

At the moment though my main concern is getting the key input handling sorted. I'll have to find out what keys the Forth code is actually expecting to be returned. Lastnight I was returning Windows VK_SHIFT (0x10) etc which it certainly won't want but I'm still not sure what it expects for delete/backspace and the likes. I appear to be filtering out non-alphanumeric characters as well such as + and -, so I'll sort that out tonight hopefully. Since I'm detecting raw keycodes (including keypress down, keypress up) I need to clean up what I return to Forth.

Re: 6502 TaliForth2 Implementation Questions

Posted: Tue Feb 25, 2025 7:29 pm
by SamCoVT
SparkyNZ wrote:
BTW I love that PDF manual you guys have put together! I'll definitely have to print myself a copy of that and get it ring-bound.

At the moment though my main concern is getting the key input handling sorted. I'll have to find out what keys the Forth code is actually expecting to be returned. Lastnight I was returning Windows VK_SHIFT (0x10) etc which it certainly won't want but I'm still not sure what it expects for delete/backspace and the likes. I appear to be filtering out non-alphanumeric characters as well such as + and -, so I'll sort that out tonight hopefully. Since I'm detecting raw keycodes (including keypress down, keypress up) I need to clean up what I return to Forth.
The manual is in the middle of getting some updates for changes made recently, so you may want to hold off on the spiral bound version, but thanks for the compliment. Scot Stevenson, the original author, was big on documentation and put a lot of effort into the manual. I try to keep it up to date and I also recently added some tutorials (including the "Tutorial: Interfacing with Hardware" section that you might find interesting).

Tali is expecting ASCII characters. Backspace can either be the backspace character (ASCII value 8 - looks like VK_BACK will work for that) or the ASCII DEL character (ASCII value 127 - Windows seems to have this as VK_F16 in case you have one of those keyboards with tons of function keys on it) - both have the same backspace behavior. Other special characters include CTRL-N (ASCII 14) and CTRL-P (ASCII 16) which allow recall of the last 8 lines of text that has been entered - that's not a critical feature for getting started. You also don't need both upper and lower case to get started (Tali is not case sensitive), but you will need a lot of symbols, such as +-*/!@#$%()\:;.?=<>'
If you have the following symbols working for input:
: . " ' = ; \
then you can try this code to see what Tali is actually getting from your simulation software.

Code: Select all

\ Use DECIMAL or HEX to set the ASCII value display base
\ before running.  Tali starts in DECIMAL mode.
: check-keys
  CR ." Press Q to quit." CR
  ." ASCII VALUE  CHAR" CR
  ." -----------  ----" CR
  begin
    key        \ Get a character.
    dup  5 u.r \ Print ASCII value in first column, right justified.
    10 spaces  \ Move to the second column.
    dup  emit  \ Print the character.
    cr         \ Go to the next line.
    
    'Q' =      \ Check to see if it's the Q key.
  until ;

\ Run it.
check-keys

Re: 6502 TaliForth2 Implementation Questions

Posted: Fri Feb 28, 2025 5:18 am
by SparkyNZ
SamCoVT wrote:
The manual is in the middle of getting some updates for changes made recently, so you may want to hold off on the spiral bound version, but thanks for the compliment. Scot Stevenson, the original author, was big on documentation and put a lot of effort into the manual. I try to keep it up to date and I also recently added some tutorials (including the "Tutorial: Interfacing with Hardware" section that you might find interesting).
Will do - looking forward to it :)

It looks as though my basic keyboard handling is almost there. As you can see I'm using the C64 font so the backslashes are appearing as pound signs.. but I can sort that out any time. I'll just hack the 8 bytes in the character ROM into a backslash :-)

One of the things I would like to do is use my "C64 style" screen editor like I did with BBC BASIC if possible. I just means that you can move the cursor up and around the screen and enter lines once again - just like the C64 used to. I hacked the BBC BASIC somewhat to make this work too - removing the ">" prompt and the likes. So it would be good to identify where the line buffer is within Tali and only process the "execute" (CR) action once something has been directly copied into the line buffer.

I'm also wondering if it will be possible to implement both legacy style page save/load/editing as well as a modern take on that.. but I'll learn to crawl first before asking about running.
TaliFails1.png
Of course it would be the very last line that fails haha (note to self - implement the save/load state for this). Any idea what I can look for there? Perhaps there's something I haven't implemented? I can easily give you a hex dump of anything or any memory you're interested in seeing.

It looks as though it isn't happy with simple compilations either:

Code: Select all

: star emit 42 ; Data stack underflow
Return stack: 81 8D 93 D0 9A 80
Hmm. This is interesting too - it must be me, surely?
Tali_c65_Fail1.png

Re: 6502 TaliForth2 Implementation Questions

Posted: Fri Feb 28, 2025 2:02 pm
by SamCoVT
The last one is you, but it's a simple fix - you just need to put the 42 on the stack before calling EMIT.

Code: Select all

: star emit 42 ;  ok
star Data stack underflow
Return stack: 4F 86 0B 08 94 87 0C CE 91 80 \ My addresses will be slightly different than yours
: star 42 emit ; redefined star  ok
star * ok
The first couple, however, are problematic. You shouldn't be getting a Data Stack Underflow error when in compiled mode - that usually only shows up in interpreted mode because many of Tali's internal words skip the underflow check. Because everything up to that line compiled properly, I'm going to guess it's the line with the 'Q' = that causes the error (Tali lets you enter an entire line before trying to interpret or compile it). I do find it odd that your star word didn't compile (even with the 42 in the wrong place).

You can look in docs/platformname_listing.txt (where platformname is the name you used on your platform file) to use the return stack trace info. The addresses will be two bytes larger than the address of the jsr that calls the subroutine, so subtract 2 before searching by address. I see (reading backwards) the addresses 809A, D093, and 8D81.

809A appears to be in QUIT (this, oddly enough, is the word that actually starts the Forth interpreter/compiler) where it calls INTERPRET. REFILL has already been called, so the entire line has already been read into the input buffer.

D093 is in the main INTERPRET loop where it calls PARSE-NAME. This looks through the input buffer to try to find the separate words.

8D81 where underflow_1 is called at the beginning of PARSE - the underflow-1 routine makes sure there is at least 1 item on the stack.

That's actually a very odd place to run into an error, because PARSE is part of PARSE-NAME. The difference is that PARSE lets you pick the character that words are separated with, while PARSE-NAME always uses spaces. The reason why this is a very odd place to have an error is because PARSE-NAME just placed the "space" character (ASCII value 32) on the stack and then it falls into PARSE.

The underflow_1 routine will complain and give a stack trace when the value in the X register (which holds the Forth data stack pointer) indicates there is less than 1 item on the stack (in this case, when X > $77). Do any of your I/O routines touch the X register? If so, they will need the PHX at the beginning and PLX at the end. That's the only thing I can think of that would be messing with the data stack pointer. Can you share the platform file you are using for your hardware that has your I/O routines?

(edited to fix typos)

Re: 6502 TaliForth2 Implementation Questions

Posted: Fri Feb 28, 2025 4:24 pm
by SamCoVT
To help with your screen editor approach, you have several options. The assembly variables you are interested in (all declared in definitions.asm) are:
cib - address of current input buffer - often but not always pointing to buffer0 (which is the default input buffer, at $200 by default and extends $200-$2FF)
ciblen - 16-bit length of the current input buffer
toin - the 16-bit index (zero based) into cib that parsing has progressed. Set to zero to start processing a line. Set to whatever value is in ciblen to finish processing a line. This can be accessed from within Forth using the variable >IN.

The Forth words you are potentially interested in (let me know if you are not familiar with the stack comments)
ACCEPT ( addr n -- n ) - Takes an address and length of buffer to put characters into. Returns number of characters placed in that buffer.
REFILL ( -- f ) - Refills the input buffer (calls ACCEPT to do that) and returns TRUE if was able to get input, FALSE otherwise. A blank line is valid input and returns TRUE.
INPUT ( -- addr ) Returns address of input vector that points to input routine. You can put a different address at this location to use a different input routine. By default, is uses your kernel_getc routine, but it can be replaced at any time while Tali is running.

Your options include (but are not limited to):
A: Modify ACCEPT to use your screen editor and copy the line where ENTER was pressed into the given buffer location (will have to handle the case where the line has more characters than the provided buffer has). ACCEPT is also used in regular forth words to get input from the user, so this would let you use the screen editor for all input.

B: Modify REFILL so that, instead of calling ACCEPT, it uses the screen editor and when ENTER is pressed it adjusts cib and ciblen to match the address and length of the current line, and then returns TRUE. Tali will then commence parsing that area of memory and will call REFILL again when it runs out of input. This would let regular input (eg. a program asking for input using ACCEPT or KEY) get the characters directly (using the unmodified ACCEPT or KEY and whatever I/O routine you are using now) while using the screen editor for general interpreting/compiling. This will also have some interaction with S" which is used to create constant strings (eg. S" This is a string") - that word can handle strings longer than 255 bytes even though Tali's input buffer is only 255 bytes long. It will keep calling REFILL to get more until it finds the ending double quote. I think that should work OK with your screen, but you'll need to consider how to handle lines longer than your screen width.

C: Modify the kernel_getc to return chars from the screen, one at a time, and a CR at the end. When no line is actively being sent, it would use the screen editor to edit/select a new line to send. This will need some extra variable to keep track of whether it is actively sending a line and where in the line it currently is. This requires the least modifying of Tali's internal words, but would also affect KEY, which is normally used to get a single key immediately (it will wait for a keypress if none are available). If you want to go this route, it's worth noting that Tali only uses the first half of zero page by default, so you can safely place any variables you need in the upper half of zero page (addresses $80-$FF).

If you need assistance with any of those, just let me know which you are trying to do.

Re: 6502 TaliForth2 Implementation Questions

Posted: Fri Feb 28, 2025 6:39 pm
by SamCoVT
SparkyNZ wrote:
I'm also wondering if it will be possible to implement both legacy style page save/load/editing as well as a modern take on that.. but I'll learn to crawl first before asking about running.
I missed this comment in my first readthrough. Once you get the base Tali system able to compile words properly, you can check out Blocks (see "Tutorial: Working with Blocks" in the manual). This is the simplest method for saving text that can be updated or run later. You can look at the source for the c65 simulator included with Tali to see how it simulates block storage using a file on the PC. Tali comes with a super simplistic "screen" editor (that's Forth "screens", which are just 1K blocks that hold Forth code) that allows editing of blocks in a line-by-line way, but you certainly could modify things to use your full-screen editor instead. You just need to be able to load or save 1K chunks of data.

That's the legacy version, which Tali has full support for. It works very well on real hardware with storage like EEPROM, FLASH, or SDCard and just a simple driver (which can be written in Forth - I have examples if you would like to see them).

Modern Forths use the File-Access word set, which Tali currently has no support for. I have written some of the words (in Forth) for read-only access to a FAT32 file system on a compact flash card, but I'd probably rewrite it as FAT16 support if I had to do it again.

Also, if you have a good way to "paste" text into your simulator, a lot of folks save/edit their source code on the PC and then just copy it into the running Forth.

Re: 6502 TaliForth2 Implementation Questions

Posted: Fri Feb 28, 2025 7:31 pm
by SparkyNZ
SamCoVT wrote:
The last one is you, but it's a simple fix - you just need to put the 42 on the stack before calling EMIT.
That doesn't surprise me. I thought I'd copied directly from my Starting Forth book but it was at the end of the day. Thanks for that one :)
SamCoVT wrote:
The underflow_1 routine will complain and give a stack trace when the value in the X register (which holds the Forth data stack pointer) indicates there is less than 1 item on the stack (in this case, when X > $77). Do any of your I/O routines touch the X register? If so, they will need the PHX at the beginning and PLX at the end. That's the only thing I can think of that would be messing with the data stack pointer. Can you share the platform file you are using for your hardware that has your I/O routines?
I can't really share any of the I/O or platform files because I'm just using the taliforth-py65mon.bin that's already been built (exact same one that C65 is using).

My Windows app sniffs the PC. When it's$F02E, I either:
1) Block for 1000 ms
2) Return immediately if a key has been pressed

The only thing my HandleKernelGetc() actually does is place the ASCII key into $F004 (or 0 if no key was pressed after 1000ms). This is done pre-CPU instruction execution (so before the command at $F02E is actually executed):

Code: Select all

      DoPreCPUExecution( addr );
      
      if( gDisassembleOn )
        Logf("PC: %04X: %s", f6502.cpu.pc, dissassemble( &ram[ addr ], f6502.cpu.pc) );

      // Execute a 6502 instruction..
      fake6502_step(&f6502);
FYI ram is just a 64k array of bytes, so ram[ 0xF004 ] is getting the keypresses. But yeah, no register manipulation is going on

Once I wake up I'll try and correlate what you provided above in regard to the stack dump. By rights, I should reassemble the C65 .asm and make sure I'm up to date with the .lst file.

Re: 6502 TaliForth2 Implementation Questions

Posted: Sat Mar 01, 2025 1:32 am
by SparkyNZ
SamCoVT wrote:
8D81 where underflow_1 is called at the beginning of PARSE - the underflow-1 routine makes sure there is at least 1 item on the stack.
..
The underflow_1 routine will complain and give a stack trace when the value in the X register (which holds the Forth data stack pointer) indicates there is less than 1 item on the stack (in this case, when X > $77).
OK.. so I have done some more dabbling. I had to add some "automated typing" to my app (similar to copy/paste) and here's what I'm trying to execute at the moment.. thinking it should be a smaller piece of code to focus on:

Code: Select all

: check-keys
  begin
    key
    'Q' =
  until ;
This time I get:

Code: Select all

: check-keys  compiled
 begin  compiled
    key  compiled
    'Q' =  compiled
  until ; Data stack underflow
Return stack: D0 95 D6 87 E7 D0 9A 80
I've added some more labels to my disassembly to try and figure out what's going on (see attached pds2.txt log file).

Code: Select all

    140063 PC: 95CE: jsr $d112:.underflow_1                                                          ; xt_until
    140064 PC: $d112  X: $78
    140065 PC: D112: cpx #$77                                                                        ; underflow_1
    140066 PC: D114: bpl $10 ; Branch to ($d126:.underflow_error)
    140067 PC: D126: lda #$09                                                                        ; underflow_error
Line 140064 is showing that the X register is $78, in which case the underflow error is taking place.

I'm thinking the best thing to do now is dump out the contents of the stack after each "compiled" output?

I'm assuming dsp0 is the stack pointer. My brain has come to an end today unfortunately, so I will have to come back to this either later or tomorrow.

Re: 6502 TaliForth2 Implementation Questions

Posted: Sat Mar 01, 2025 10:42 am
by BigDumbDinosaur

Code: Select all

11 PC: 8011: bne $f8 ; Branch to ($800b)

That’s not making sense to me.  The disassembly of any branch instruction should show the target address as the operand, not the relative offset.  What should be seen in the above case is BNE $800B.

Code: Select all

    140065 PC: D112: cpx #$77                                                                        ; underflow_1
    140066 PC: D114: bpl $10 ; Branch to ($d126:.underflow_error)

Using BMI or BPL immediately following a comparison can lead you astray.  A comparison, e.g., CPX #$77, is actually an unsigned subtraction in which the difference is discarded and the c, n and z flags in SR are conditioned.  The state of the n flag, which is what BMI and BPL are testing, is conditioned by what the difference would have been if it had been retained.  For example, the following code would not work as expected:

Code: Select all

         lda #$00
         cmp #$81
         bmi overhere          ;branch won't be taken

$00 is clearly “less-than” $81, but $00 - $81 = $7F, which is a positive number from the MPU’s perspective, due to bit 7 being cleared.

If your intention is to do a greater-than/lesser-than comparison, you need to use a combination of BCC, BCS, BEQ and/or BNE to get reliable results.

Re: 6502 TaliForth2 Implementation Questions

Posted: Sat Mar 01, 2025 3:13 pm
by SamCoVT
Hi BDD,
The value being checked here should never be larger than $78, so I think the signed comparison is fine in this case. It's not the comparison that is causing any trouble here, but rather than the value in X (used as the data stack pointer in Tali Forth) is $78 (empty stack) when it should be $76 (one item on stack) at this point in the code.

While I don't think it's an issue here, I will put an issue into the tracker for that. Tali Forth allows relocating the stack to anywhere in zero page and it will be an issue if the stack crossed $80. Thanks for mentioning it.

SparkyNZ,
The dsp0 label is a constant value that the stack pointer (held in X) has for an empty stack. It has the address of $78 in the default zero page configuration (which you are using) where Tali only uses the lower half of zero page and leaves the upper half for the user to use. You can find the actual label in definitions.asm if you need it, but all you really need to know is that the data stack is in zero page and it grows downwards as items are placed on the stack. Each stack cell is 16-bits (two bytes) so X would have the value $76 when there is one item on the stack and $74 with two items, etc.

Your trace shows there is an error when it reaches the word UNTIL in the code. This word is expecting the address of the beginning of the loop to be on the data stack (some Forths have a separate flow control stack but Tali just uses the data stack, which is common practice). That address should have been put there when BEGIN was encountered up at the top of the loop. Somewhere along the way, it has been removed from the stack.

Can you verify that the Forth code you are trying to compile works in either c65 or py65mon? If so, that would point to an issue with your simulator. I will double-check today and verify the version you are using works on real hardware.

I believe you had mentioned that you are using the same fake65c02.h library that we are using for c65, but I will warn you that there are multiple versions out on the interwebs and not all simulate 65C02 properly. The version that comes with c65 has been modified slightly to pass all of Klaus Dormann's 65C02 tests (a series of tests used to test simulators for correct behavior). If you haven't heavily modified that file, you may want to try switching to the copy that c65 is using as it's known-good.

You'll also want to verify you are using a very recent version of the tass64 assembler (version 1.56.2625 or later should work). Some flavors of Ubuntu, in particular, ship a very old version. I usually just compile it from source, which is super easy because it has virtually no dependencies and it looks like you definitely know how to use a C compiler.

Tali also comes with a full test suite that tests about 90% of its functionality. It looks like you are able to compile the c65 simulator, so you can run make ctests to assemble Tali for c65 and run the entire test suite in that simulator. It should take a few seconds and will print a message that all tests passed successfully or will give you a list of the tests that failed.

(edited to fix typos)

Re: 6502 TaliForth2 Implementation Questions

Posted: Sat Mar 01, 2025 11:22 pm
by SparkyNZ
SamCoVT wrote:
Can you verify that the Forth code you are trying to compile works in either c65 or py65mon? If so, that would point to an issue with your simulator. I will double-check today and verify the version you are using works on real hardware.
Yes, it definitely works fine with C65 and that's using the same .bin file. I can confirm that the fake6502 library I have is completely different though. I couldn't find a fake6502.c (or .cpp) file in the C65 source - it looks like it's all in the fake65c02.h header file.

I'm going to get some fresh air today but when I come back I'll try and make some time to see if replacing the fake6502 code will get it to work. It would be good just to tick the box and say "it works" but I'd also be interested to drill down a bit more and find out why. That version of fake6502 seems to be at least a 2022 version:

Code: Select all

In 2020, this fork was created:

<a href='https://github.com/omarandlorraine/fake6502'>https://github.com/omarandlorraine/fake6502</a>


See: <a href='./CHANGELOG.md'>CHANGELOG.md</a>

- - -

\section f6502_version Version

v2.4.0 - 19-07-2022
SamCoVT wrote:
You'll also want to verify you are using a very recent version of the tass64 assembler (version 1.56.2625 or later should work). Some flavors of Ubuntu, in particular, ship a very old version. I usually just compile it from source, which is super easy because it has virtually no dependencies and it looks like you definitely know how to use a C compiler.
Noted. I used ca65 to build the BBC source but don't mind giving tass65 a go when the time comes. At the moment I'm pretty much squirelling things away for my homebrew system for later on. Looking at Forth has been a bit of a break from hardware projects but I'd like to know that I have something that works in my emulated 6502 environment before I parking it and forgetting about it for a while.

Thanks for the tip on the tests - I'm sure ca65 will pass all the tests. I'll knock up another branch later with ca65's version of fake65c02.h and see how that goes.

Re: 6502 TaliForth2 Implementation Questions

Posted: Sat Mar 01, 2025 11:25 pm
by SparkyNZ
SamCoVT wrote:
To help with your screen editor approach, you have several options.
Your options include (but are not limited to):
A: Modify ACCEPT to use your screen editor and copy the line where ENTER was pressed into the given buffer location (will have to handle the case where the line has more characters than the provided buffer has). ACCEPT is also used in regular forth words to get input from the user, so this would let you use the screen editor for all input.

B: Modify REFILL so that, instead of calling ACCEPT, it uses the screen editor and when ENTER is pressed it adjusts cib and ciblen to match the address and length of the current line, and then returns TRUE. Tali will then commence parsing that area of memory and will call REFILL again when it runs out of input. This would let regular input (eg. a program asking for input using ACCEPT or KEY) get the characters directly (using the unmodified ACCEPT or KEY and whatever I/O routine you are using now) while using the screen editor for general interpreting/compiling. This will also have some interaction with S" which is used to create constant strings (eg. S" This is a string") - that word can handle strings longer than 255 bytes even though Tali's input buffer is only 255 bytes long. It will keep calling REFILL to get more until it finds the ending double quote. I think that should work OK with your screen, but you'll need to consider how to handle lines longer than your screen width.
It will be one of these two - may be a few weeks down track though. Got family coming to stay for a bit so I'll need to be sociable.. or at least try :-)

Re: 6502 TaliForth2 Implementation Questions

Posted: Sun Mar 02, 2025 12:42 am
by SamCoVT
SparkyNZ wrote:
I can confirm that the fake6502 library I have is completely different though. I couldn't find a fake6502.c (or .cpp) file in the C65 source - it looks like it's all in the fake65c02.h header file.
That's correct - it's all in the header file. That version of fake6502 came from the Commander X16 project, and I believe they made edits as well. While the Commander X16 project has a different license (although a quick look shows at least one of the developers is changing to a public domain license), the fake6502 header file still had a public domain header at the top, so we are assuming the changes they made are also public domain.

It sounds like you are on a reasonable path that has a good chance of success.

If/when you get to the point where you want to think about integrating your screen editor, I'll likely still be here and you're welcome to revive this thread (even if it's ancient by then).

Re: 6502 TaliForth2 Implementation Questions

Posted: Wed Mar 05, 2025 10:25 pm
by SparkyNZ
SamCoVT wrote:
It sounds like you are on a reasonable path that has a good chance of success.
I had a bit of spare time.. I tried a few things but ultimately I replaced the Fake6502 with the Fake65C02 version from C65 and here's the result:
TaliWorks1.png
(It works!)

The first keypress checking program you gave me works.. but I'm still seeing "compiled" appearing twice for each line when I read in text from file, so I have a bit of tidying up to do there. When I get that sorted I'll see if I have some spare time to start looking at the line-entry changes.