6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Nov 22, 2024 5:30 am

All times are UTC




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: uxForth for the VIC-20
PostPosted: Tue Nov 08, 2016 2:51 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10985
Location: England
Julian Skidmore aka Snial is writing about writing a Forth for the VIC-20 - a classic small-RAM 6502 keyboard-sized computer.

Part 1, the appeal of the VIC-20.

Image

Image

Part 2, the different types of Forth interpreter.

Image

(For me, these diagrams are much more digestible than a textual description.)


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 06, 2016 7:02 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Consider a hybrid of the two. A secondary would be a list of one-byte or two-byte tokens
  • One byte (with the sign bit set) treated as an index into a 128-element table of addresses of most common words
  • Two bytes, (with the sign bit clear), to be treated as the high address byte
    The second byte is the lower address byte

The inner interpreter NEXT routine might look something like:
Code:
; somewhere on the zero page, allocate 3 bytes
iplo
   .byte 0
ippage
   .word $0400

...
; this code goes elsewhere
next
   inc iplo            ; bump the instruction pointer
   bne noflip
   inc ippage            ; flip to the next code page
noflip
   ldy iplo
   lda (ippage),y         ; fetch the instruction
   bpl twobytes         ; positive?  this is addr high byte
onebyte
   tay                  ; negative? this instruction is an index
   lda wordshi-$80,y      ; subtract 128 to take the sign bit into account
   pha
   lda wordslo-$80,y
   bne out               ; bra
twobytes
   pha
   iny
   sty iplo            ; finish up the two-byte addresses
   lda (ippage),y
out
   pha
   rts


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 1:11 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
BigEd wrote:
(For me, these diagrams are much more digestible than a textual description.)
The diagrams *are* well done. They're found at the second of the two links Ed posted.


chitselb wrote:
Consider a hybrid of the two. [...] The inner interpreter NEXT routine might look something like:
Something like, yes. :P

But I think...
Code:
   inc ippage            ; flip to the next code page
... should be...
Code:
   inc ippage+1            ; flip to the next code page

Also, when a two-byte token is fetched there's another increment of the pointer at ippage -- but it's just an 8-bit increment. It's lacking a test for roll-over into the high byte.

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 1:32 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Quote:
chitselb wrote:
Consider a hybrid of the two. [...] The inner interpreter NEXT routine might look something like:
Something like, yes. :P

wups, yes ippage+1. contents of ippage is always a 0, and the pointer within that page is in Y

Quote:
But I think...
Code:
   inc ippage            ; flip to the next code page
... should be...
Code:
   inc ippage+1            ; flip to the next code page

Also, when a two-byte token is fetched there's another increment of the pointer at ippage -- but it's just an 8-bit increment. It's lacking a test for roll-over into the high byte.

True. In PETTIL (a direct-threaded Forth) I'm used to having the compiler insert the word PAGE (to increment IP+1) within a secondary when it crosses a page boundary. That might be possible here too.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 1:40 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
chitselb wrote:
In PETTIL (a direct-threaded Forth) I'm used to having the compiler insert the word PAGE (to increment IP+1) within a secondary when it crosses a page boundary.
I wondered about that. It's an idea worth remembering.

Quote:
That might be possible here too.
Yup. And it could be applied in all cases -- ie, regardless of whether the page crossing results from the fetch of an 8-bit or of a 16-bit token.

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 4:20 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Quote:
Yup. And it could be applied in all cases -- ie, regardless of whether the page crossing results from the fetch of an 8-bit or of a 16-bit token.
The intended tradeoff here is lose clocks to gain bytes, so we can squeeze more code into a stock 5K VIC-20, so a one-byte PAGE token is the way to go. But include the ability to have more than 128 words in the dictionary. I can imagine using this approach in some type of target-compiled project. It should also be noted that two-byte addresses within a secondary are stored in big-endian format, which is the opposite of the 6502's native little-endianness. Here's a hopefully improved partial inner interpreter implementation
Code:
; instruction pointer takes up 3 zero page locations
iplo
   .byt   0
ip
   .word $0400

page
   inc ip+1
next
   inc iplo
   ldy iplo
   lda (ip),y
   bmi token
direct
   pha
   iny
   sty iplo
   lda (ip),y
   pha
   rts
token
   tay
   lda tokenh-128,y
   pha
   lda tokenl-128,y
   pha
   rts

tokenh
   .byt >(page-1)
tokenl
   .byt <(page-1)


Last edited by chitselb on Wed Dec 07, 2016 5:37 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 5:24 pm 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
Instead of making a 128/128 split, you could also start out with 256 single-byte codes. As the table gets full, the last entry is set to point to a secondary dispatcher using a new table.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 5:51 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Arlet wrote:
Instead of making a 128/128 split, you could also start out with 256 single-byte codes. As the table gets full, the last entry is set to point to a secondary dispatcher using a new table.
This all-token model makes more sense for an unexpanded VIC-20 (5K) than a maxed-out PET (32K) which neatly divides the RAM from the ROM + I/O at the 32K boundary.

Including the Assembler and a bunch of other stuff that won't be in the final edition, PETTIL has about 600 words in it right now (approximately 5 screens, 5 columns, 25 lines per screen). Even a minimalist Forth plus any application code would push a 256 token limit. It's pretty easy to add a token that treats the following two bytes as an address.

One could even use the BRK instruction as a way to toggle back and forth (get it?) between separate 8-bit and 16-bit NEXT routines, e.g. token1 token2 token3 00 addr1 addr2 00 token1 ...


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 07, 2016 7:19 pm 
Offline

Joined: Thu Jan 21, 2016 7:33 pm
Posts: 282
Location: Placerville, CA
Heck, make the "long token" signifier $00 and you can just short-circuit the dispatch routine with a BEQ when it's loaded from memory. (Or, if you're doing the 128/128 split, a BMI - depends on whether you want your long tokens to be two bytes or three, I suppose.)


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 08, 2016 7:59 am 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 679
If "long tokens" are more rarely executed than short tokens, it makes sense to keep the fast path fast, and not have extra checks whether or not to branch out. Nested dispatches keep the outer dispatch as lean as possible.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 08, 2016 9:52 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
White Flame wrote:
If "long tokens" are more rarely executed than short tokens, it makes sense to keep the fast path fast, and not have extra checks whether or not to branch out. Nested dispatches keep the outer dispatch as lean as possible.

Yes, that's exactly what I was thinking. Most likely the first 250 or so tokens are executed much more frequently than the rest. They are also likely smaller so the relative gain of a few cycles matters more.


Top
 Profile  
Reply with quote  
PostPosted: Thu Mar 09, 2017 8:22 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10985
Location: England
Part 3 of the series is up...
Quote:
I've made use of the wonderful resources at 6502 org, particularly the idea of splitting the instruction pointer (called gIp in my implementation) into a page offset and using the Y register to hold the byte offset: it really does improve the performance of the core Next function.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron