Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

The reason I was interested in looking at Wycove Forth because I had heard that it had 24x40 column editor, in other words, the editor fits the TI-screen.

 

I had written an editor that let you cursor around the screen and edit and interpret lines from the VDP memory.

It was a short journey to save the VDP screen to a Forth block. 

The result is a very efficient editor for the TI-99 if you are ok with block files and 40 columns of width.  This version takes 1694 bytes of system memory.

 

I decided to play with the idea of treating the screen like one big line buffer so you can insert/delete text and affects the entire screen.

Not always ideal. It should probably have a way to enable and disable that feature.

 

Of course the problem is that regular 64 column Forth source code does not look correct on this editor so it's one or the other.  :)

It was an interesting exercise. I formatted the editor code for this editor and it looks good to me.

 

Spoiler

\ Experimental VDP Screen editor
\ with block files Oct 21, 2021
NEEDS DUMP FROM DSK1.TOOLS
NEEDS CASE FROM DSK1.CASE
NEEDS RKEY FROM DSK1.RKEY
NEEDS BLOCK FROM DSK1.BLOCKS
NEEDS VOCABULARY FROM DSK1.WORDLISTS

VOCABULARY EDITOR
ONLY FORTH ALSO EDITOR DEFINITIONS
HERE
\ cursor characters
HEX
201F CONSTANT BOX
201E CONSTANT BAR

VARIABLE INSERT
VARIABLE SCR

\ cheap & dirty dynamic memory
: RESERVE ( n -- addr) HERE  ALLOT ;
: CANCEL  ( n -- )    NEGATE ALLOT ;

\ CREATE IBUFF  C/L@ ALLOT
DECIMAL
L/SCR 1- CONSTANT BOT
C/L@  1- CONSTANT WIDTH

: BLANK   BL FILL ;
: CLRLN  ( X Y ) >VPOS C/L@ BL VFILL ;

\ text manipulation
: SCREND ( -- Vaddr)
  VPG @  [ C/SCR @ 1- ] LITERAL + ;

\ track row/col when cursors moves
\ more than C/L length
: R/C-UPDATE
  VCOL @ C/L@ >
  IF
    VCOL @ C/L@ /MOD  VROW ! VCOL !
  THEN ;

\ cursor movement controls
: CRIGHT
  VPOS SCREND < IF VCOL 1+! THEN
;

: LIMIT1+! ( addr limit --)
  OVER @ 1+  MIN SWAP !
;

: CDOWN
  VROW @ 23 <
  IF
  VROW C/SCR @ 1- LIMIT1+!
  THEN
;

: CUP    VROW @ 1-  0 MAX VROW ! ;
: CLEFT  VCOL @ 1-  0 MAX VCOL ! ;

: NEWLINE VCOL OFF CDOWN ;

HEX
: INS/DEL
  INSERT @ -1 XOR INSERT !
  INSERT @ IF 201E ELSE 201F
  THEN CURS ! ;

\ Screen text functions
DECIMAL
: BYTES-BELOW ( Vaddr -- Vaddr len)
  C/SCR @ OVER - ;
: RIGHTSIDE ( -- Vaddr len)
  VPOS BYTES-BELOW ;

: LEFTSIDE ( -- Vaddr len)
  VPG @  VPOS  ;

: VCOPY ( vaddr len addr -- )
  SWAP VREAD ;

: /CHARS ( addr n)
  /STRING TUCK
  HERE VCOPY
  HERE VPOS ROT VWRITE
  BL SCREND VC!
;

: PUSHRIGHT ( Vaddr len --)
  TUCK HERE VCOPY
  BL VPUT
  HERE VPOS 1+ ROT VWRITE ;

: DELCHAR ( -- ) RIGHTSIDE 1 /CHARS ;
: INSCHAR ( -- ) RIGHTSIDE PUSHRIGHT ;

: DEL-LINE
  0 VROW @ 1+ >VPOS BYTES-BELOW
  ( Vddr len) DUP>R HERE VCOPY
  HERE  0 VROW @ >VPOS R> VWRITE
  0 23 CLRLN ;

: INS-LINE
  0 VROW @ 2DUP ( col row col row)
  >VPOS BYTES-BELOW C/L@ -
  DUP>R HERE VCOPY
  HERE  0 VROW @ 1+ >VPOS R> VWRITE
  ( col row) CLRLN
;

DECIMAL
: LASTXY ( -- col row) 0 BOT ;
: SAVELN
  HERE C/L@ BLANK ;
  LASTXY >VPOS HERE C/L@ VREAD ;

: PROMPT:
  SAVELN LASTXY 2DUP CLRLN AT-XY ;

: GETXY  ( col row) VROW 2@ ;

: BLIT ( addr --)
  VPG @   C/SCR @ VWRITE ;

: GET-SCR  SCR @ BLOCK  BLIT ;

\ read VDP screen into BLOCK buffer
: (SAVE)
  VPG @  SCR @ BLOCK C/SCR @ VREAD
  UPDATE FLUSH ;

: SAVE-SCR  ( -- )
  (SAVE) GETXY
  PROMPT: ." Saved" 700 MS
  GET-SCR   AT-XY ;

: .BLK#
  GETXY
  PROMPT: ." Blk " SCR @ . 100 MS
  GET-SCR AT-XY ;

: PGDN  SCR 1+! .BLK# GET-SCR ;

: PGUP SCR @ 1- 0 MAX SCR !
      .BLK# GET-SCR ;

\ screen colors + black letters
HEX
17 CONSTANT CYAN
12 CONSTANT GREEN
1E CONSTANT GRAY
: SCREEN ( C --)  7 VWTR ;

: ESCAPE
  (SAVE)
  [CHAR] _ CURS !
  E4 SCREEN
  PROMPT: ." Forth"
  FLUSH ABORT ;

: PUTCHAR ( c --)
  INSERT @ IF INSCHAR
  THEN VPUT CRIGHT
;

HEX
: KEYHANDLER ( char -- )
  CASE
   0D OF  NEWLINE         ENDOF
   0C OF  PGUP            ENDOF
   02 OF  PGDN            ENDOF
   06 OF  INS-LINE        ENDOF
   07 OF  DEL-LINE        ENDOF
   08 OF  CLEFT           ENDOF  \
   0B OF  CUP             ENDOF
   0A OF  CDOWN           ENDOF
   09 OF  CRIGHT          ENDOF
   03 OF  DELCHAR         ENDOF
   04 OF  INS/DEL         ENDOF
   10 OF  PAGE UPDATE     ENDOF \ ^P
   13 OF  SAVE-SCR        ENDOF \ ^S
   15 OF  GET-SCR ( undo) ENDOF \ ^U
   0F OF  ESCAPE          ENDOF
          HONK
   ENDCASE
  \ remove spurious output
   KEY? DROP
;

HEX
\  user interface
: EDIT  ( BLK#  -- )
     PAGE
     GRAY SCREEN
     SCR !
     GET-SCR
     BOX CURS !
     LINES OFF
     BEGIN
       RKEY 7F AND
       DUP  BL [CHAR] ~ 1+ WITHIN 0=
       IF   KEYHANDLER
       ELSE PUTCHAR
       THEN
     AGAIN ;

: USE   PARSE-NAME OPEN-BLOCKS ;

DECIMAL
\ added LOAD so we can compile code from BLOCKS
 VARIABLE SCR
: LINE ( n -- addr) C/L@ *  SCR @ BLOCK  + ;

: LOAD ( n -- )
  SCR !
  23 0 DO
     I LINES ! ( keep track of line number for errors)
     I LINE C/L@  EVALUATE
  LOOP ;

DECIMAL HERE SWAP - .

 

 

 

  • Like 2
Link to comment
Share on other sites

1 hour ago, TheBF said:

The reason I was interested in looking at Wycove Forth because I had heard that it had 24x40 column editor, in other words, the editor fits the TI-screen.

 

I had written an editor that let you cursor around the screen and edit and interpret lines from the VDP memory.

It was a short journey to save the VDP screen to a Forth block.  The result is a very efficient editor for the TI-99 if you are ok with block files and 40 columns of width.  This version takes 1694 bytes of system memory.

 

I decided to play with the idea of treating the screen like one big line buffer so you can insert/delete text and affects the entire screen.  Not always ideal. It should probably have a way to enable and disable that feature.

 

Of course the problem is that regular 64 column Forth source code does not look correct on this editor so it's one or the other.  :)

It was an interesting exercise. I formatted the editor code for this editor and it looks good to me.

 

[Because I did not fully analyze your code, I apologize in advance if you are already implementing some of the suggestions that follow.]

 

Maybe it would be useful to manage your screen-editor buffer as 1 KiB of VRAM, with the actual screen image as 15 of the 16 64-byte lines of a Forth block. You could then load one entire block there. You might need to relocate the Value Stack by changing >836E (Value Stack Pointer) to point to an unused 128-byte VRAM area.

 

It might also be convenient to use VRAM >0400 as a second, 1024-byte screen/block buffer to allow switching screens by simply changing VR02 (and your screen variables, of course).

 

...lee

  • Like 1
Link to comment
Share on other sites

6 hours ago, Lee Stewart said:

 

[Because I did not fully analyze your code, I apologize in advance if you are already implementing some of the suggestions that follow.]

 

Maybe it would be useful to manage your screen-editor buffer as 1 KiB of VRAM, with the actual screen image as 15 of the 16 64-byte lines of a Forth block. You could then load one entire block there. You might need to relocate the Value Stack by changing >836E (Value Stack Pointer) to point to an unused 128-byte VRAM area.

 

It might also be convenient to use VRAM >0400 as a second, 1024-byte screen/block buffer to allow switching screens by simply changing VR02 (and your screen variables, of course).

 

...lee

I am using the 960 byte screen at V>0000  as the editing buffer and just blast that into a BLOCK when you press ^S (SAVE)

Your ideas are all good. Not sure how much farther I will push this editor. I was really trying see what I thought of the Wycove 24x40 screen format editor concept. 

 

I might even go on to make ~10K buffer in VDP RAM for editing text files.

The code I have created literally gives me an "editline" function for VDP RAM so I could do it on each line of a Text file.

 

I have never used that value stack pointer.  I will read up on it to see if I need to touch it.

I use the top of VDP RAM at >8370 as the start of the PAB stack.

I have a Forth variable that I used to keep track of the bottom of VDP RAM.  Maybe I should be using 836E instead. ??

 

 

  • Like 1
Link to comment
Share on other sites

16 minutes ago, TheBF said:

I am using the 960 byte screen at V>0000  as the editing buffer and just blast that into a BLOCK when you press ^S (SAVE)

Your ideas are all good. Not sure how much farther I will push this editor. I was really trying see what I thought of the Wycove 24x40 screen format editor concept. 

 

I might even go on to make ~10K buffer in VDP RAM for editing text files.

The code I have created literally gives me an "editline" function for VDP RAM so I could do it on each line of a Text file.

 

I have never used that value stack pointer.  I will read up on it to see if I need to touch it.

I use the top of VDP RAM at >8370 as the start of the PAB stack.

I have a Forth variable that I used to keep track of the bottom of VDP RAM.  Maybe I should be using 836E instead. ??

 

 

This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work..

Edited by GDMike
Link to comment
Share on other sites

Just now, GDMike said:

This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work..

Bf's two rules of software always apply:

1. Anything is possible ?

2. Nothing is easy ?

  • Like 1
  • Haha 1
Link to comment
Share on other sites

3 minutes ago, GDMike said:

This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work..

That sounds more like changing the command line interpreter rather than the editor. (?)

GForth has that with the arrow keys I think.

Is that what you want?

  • Thanks 1
Link to comment
Share on other sites

Oh..btw. I can't upload videos anymore, well, videos with any type of length. Seems Albert says server can't handle it and so really didn't support it to begin with. But that's why my videos showing SNP weren't going through. Makes sense. But photos are just fine.

Link to comment
Share on other sites

1 hour ago, GDMike said:

Definitely.

I can think of ways to do that.

I may need some help from Lee but basically we just need to create a circular buffer of say 8 "terminal input buffers" that switch on the up-arrow/dn-arrow keys.

Might even be able to keep those text lines in VDP RAM so they don't take up program memory.  :)

I will noodle on it. 

  • Thanks 1
Link to comment
Share on other sites

1 hour ago, TheBF said:

That sounds more like changing the command line interpreter rather than the editor. (?)

GForth has that with the arrow keys I think.

Is that what you want?

This is wonderful. Wishing for TF had it too. 

I can't wait to finish SNP... really.. I know I've said this before, but I feel SNP isn't done yet. Getting close. Because I'll end up in Forth. Just not the rt time yet. BECS I figure, if I go into forth I don't want something outside forth on my mind in the background trying to pull me away from a Forth project (mindset). 

Edited by GDMike
Link to comment
Share on other sites

3 hours ago, TheBF said:

I have never used that value stack pointer.  I will read up on it to see if I need to touch it.

 

The Value Stack is used by functions in the console. I never use it directly, but I always try to stay out of its way. You can change it to point to any 128-byte block of VRAM—just be sure it is free VRAM!

 

3 hours ago, TheBF said:

I use the top of VDP RAM at >8370 as the start of the PAB stack.

I have a Forth variable that I used to keep track of the bottom of VDP RAM.  Maybe I should be using 836E instead. ??

 

That won’t work because there are other intervening blocks of VRAM—notably, the Pattern and Sprite Descriptor Tables.

 

...lee

  • Like 1
Link to comment
Share on other sites

On 5/14/2022 at 12:28 AM, TheBF said:

Or in some parts of the world they can do this: 


TASK: WIFE#1

TASK: WIFE#2



: CHILDREARING   BEGIN   DIAPER FEED DIAPER BATH DIAPER ROCK-TO-SLEEP   AGAIN   ;
: HOUSEHOLD       BEGIN   BREAKFAST CLEANUP SHOPPING LUNCH SUPPER RUB-MY-FEET  LAUNDRY  AGAIN ;


' CHILDREARING WIFE#1 ASSIGN

' HOUSEHOLD      WIFE#2 ASSIGN 



MULTI

WIFE#1 WAKE

WIFE#2 WAKE 

 

:) 

 

Tut tut! This code is woefully incomplete. You have failed to note the important contribution of the male of the family!

 

TASK: WIFE#1

TASK: WIFE#2

: CHILDREARING   BEGIN   DIAPER FEED DIAPER BATH DIAPER ROCK-TO-SLEEP   AGAIN   ;
: HOUSEHOLD      BEGIN   BREAKFAST CLEANUP SHOPPING LUNCH SUPPER RUB-MY-FEET  LAUNDRY  AGAIN ;

: BLOKE          BEGIN   5 HOURS 0 DO DRINK-BEER WATCH-FOOTBALL FART LOOP EAT SLEEP AGAIN ;   
' CHILDREARING   WIFE#1 ASSIGN

' HOUSEHOLD      WIFE#2 ASSIGN 

MULTI

WIFE#1 WAKE

WIFE#2 WAKE

BLOKE

 

There you go!!! Note that bloke runs in the main thread, since he requires the most attention and maintenance :ahoy:

  • Like 1
  • Haha 4
Link to comment
Share on other sites

9 hours ago, Lee Stewart said:

 

The Value Stack is used by functions in the console. I never use it directly, but I always try to stay out of its way. You can change it to point to any 128-byte block of VRAM—just be sure it is free VRAM!

 

 

That won’t work because there are other intervening blocks of VRAM—notably, the Pattern and Sprite Descriptor Tables.

 

...lee

I seem to have empty VDP RAM from >1000 up to the value in >8370.  So my VP variable (VDP pointer) inits to >1000. 

I think because I have my own DSR code and only use KSCAN and ?TERMINAL in console ROM I have been  un-affected.

I have yet to do anything in bit-map mode so that will consume almost everything I believe.

I will have to pay more attention to this. Thanks.

Link to comment
Share on other sites

12 hours ago, GDMike said:

This is wonderful. Wishing for TF had it too. 

I can't wait to finish SNP... really.. I know I've said this before, but I feel SNP isn't done yet. Getting close. Because I'll end up in Forth. Just not the rt time yet. BECS I figure, if I go into forth I don't want something outside forth on my mind in the background trying to pull me away from a Forth project (mindset). 

You can substitute the built-in TF interpreter for one of your own. Just write it in normal Forth code using colon definitions, get the address of your new interpreter using tick, and load that address into address $A000 et viola. TF is now running YOUR interpreter. Your new interpreter would include the code to store the last 4 commands entered. :thumbsup:

 

The more useful vectors for TF are as follows:

intvec  bss 2       ; vector for INTERPRET       >a000
blkvec  bss 2       ; vector for BLOCK           >a002
numvec  bss 2       ; vector for NUMBER          >a004
fndvec  bss 2       ; vector for FIND            >a006

We now return you to your regular Camel99-based programme!

  • Like 2
  • Thanks 2
Link to comment
Share on other sites

4 hours ago, Willsy said:

You can substitute the built-in TF interpreter for one of your own. Just write it in normal Forth code using colon definitions, get the address of your new interpreter using tick, and load that address into address $A000 et viola. TF is now running YOUR interpreter. Your new interpreter would include the code to store the last 4 commands entered. :thumbsup:

 

The more useful vectors for TF are as follows:


intvec  bss 2       ; vector for INTERPRET       >a000
blkvec  bss 2       ; vector for BLOCK           >a002
numvec  bss 2       ; vector for NUMBER          >a004
fndvec  bss 2       ; vector for FIND            >a006

We now return you to your regular Camel99-based programme!

I just might have to steal that idea myself. :)

 

  • Like 3
Link to comment
Share on other sites

As I continued to learn how to code efficiently on this project, the size of the Camel99 kernel slowly got smaller.

I had removed about 200 bytes so it was time to add some value back.

Something I always wanted to add, that the original Camel Forth code did not have, was the ability to check for duplicate names when you compile your code.

 

I had lots of extra room now so I am taking some back for this feature. I am doing it in the style of F83, GForth and others

where you can turn warnings on/off with this complicated syntax:

WARNINGS ON    ( turns the warnings on ) 
WARNINGS OFF   ( turns the warnings off)

:)

 

It seems to work although with warnings on the compile times are about about 28% slower because of searching the dictionary on every new definition.

Thus the WARNINGS flag to turn them off on a big compile.

 

This now has me wondering if I can duplicate the 4 way hashed dictionary like F83 to speed up the searching. 

It means adding that look up mechanism to my cross-compiler first...

That's a bit daunting. :) 

 

Here is the relevant code in from the HILEVEL.HSF file.  It will get to Github eventually.

Spoiler

\ ======================================================================
\ D I C T I O N A R Y   C R E A T I O N

TARGET-COMPILING

: HEADER, ( addr len --)
      ALIGN
      CURRENT @ @ ,        \ get last NFA & compile in this LFA field
      0 C,                 \ compile the precedence byte (immediate flag)
      HERE >R              \ save HERE (ie: new NFA location)
      S,                   \ compile (addr len) as counted string
      WARNINGS @
      IF
          R@ FIND ( xt ?) NIP ( ?)
          IF
            SPACE  R@ COUNT 1F AND TYPE  T."  isn't unique "
          THEN
      THEN
      R@ LATEST !       \ HERE now is the last word defined
      R> CURRENT @ !    \ Also store in the current 'WID'
;

: HEADER ( <TEXT> )  BL PARSE-WORD HEADER, ;

 

 

  • Like 2
Link to comment
Share on other sites

12 hours ago, TheBF said:

I just might have to steal that idea myself. :)

 

Yeah, it seemed sensible at the time. Being able to re-vector/patch FIND has proved the most useful. I use that in the local variables library IIRC. I patch the FIND vector such that it runs the locals version of FIND first, and if it doesn't find a local, it then calls the internal FIND to do a normal dictionary search.

 

I've also used the interpreter vector for a peephole optimizer that I was playing with. Patching BLOCK would allow one to store blocks in SAMS etc... Just a matter of adding the appropriate logic: Search SAMS for the block in question, if not found, call the regular in-ROM BLOCK routine instead etc.

 

There's an ISR vector too for calling machine code routines. I would have liked to be able to call Forth words with it, but I couldn't figure it out at the time!:roll:

  • Like 2
Link to comment
Share on other sites

5 hours ago, TheBF said:

This now has me wondering if I can duplicate the 4 way hashed dictionary like F83 to speed up the searching. 

It means adding that look up mechanism to my cross-compiler first...

That's a bit daunting. :) 

 

That's a good feature. TF also has that via WARN:

0 WARN ! ( turn off warnings)

1 WARN ! (turn 'em on)

 

In the locals library that I did some years back I hash the names of the locals into a 16-bit word. Since locals are only in existence during the compilation of a word (after compilation they exist as an offset into a locals stack) it doesn't make sense to refer to them by name. Instead, I hash their names during declaration, and during compilation of the word that contains a local, if the regular word isn't found I hash the word currently being compiled and search the 'locals dictionary', which is nothing more than an array of integers. If I find a match, then it's ordinal position in the array determines the offset it will have into the locals stack at run-time. Simples.

 

There's a risk of a hash-collision, particularly when using a 16-bit hash (I actually stole the MODBUS 16-bit CRC algorithm as I think it's quite a good candidate - after all, a CRC algorithm is concerned with detecting bit changes so theoretically a single bit difference will generate a completely different CRC/hash) - though I don't know much of the 16-bit 'space' the MODBUS CRC actually covers. That's one for the mathematicians. Are you reading this, @Lee Stewart;).

 

In practice though, I have not yet encountered a name collision and I've used the library quite a lot.

Edited by Willsy
added links
  • Like 3
Link to comment
Share on other sites

That's a neat usage of a hash table.  

 

The n-way hashing I am thinking about was invented in PolyForth (Forth Inc)  I believe.

Polyforth changed the dictionary from a single linked list to eight different entry points.

 

To determine the list a word will be added to, the first letter of the new word is hashed to derive a 0,1,2,3,4,5,6, or 7, or 8.

So each search times are potentially 8 times faster.

 

I am going with a 4 way linked list like F83 to save memory. Each word list requires a 4 cell array to hold the "latest" word in each list and memory is valuable on the old 99.

I am also thinking that when I do this I have made enough changes to stop calling this Camel Forth. :)

 

Edited by TheBF
Off by one error :-0
  • Like 3
Link to comment
Share on other sites

5 hours ago, Willsy said:

There's a risk of a hash-collision, particularly when using a 16-bit hash (I actually stole the MODBUS 16-bit CRC algorithm as I think it's quite a good candidate - after all, a CRC algorithm is concerned with detecting bit changes so theoretically a single bit difference will generate a completely different CRC/hash) - though I don't know much of the 16-bit 'space' the MODBUS CRC actually covers. That's one for the mathematicians. Are you reading this, @Lee Stewart;).

 

Ha! Don’t give me too much credit here.

 

Unless I miss your point, I should think that a 16-bit hash would cover all of the 16-bit space if the hash function can generate all 16-bit values. That would be a problem for our TI-99/4A if you were using the hash as an index into a table, but using it as a shortcut for parsing each name in a search should work just fine. Of course, some method of handling collisions might be warranted. The current collision check might be sufficient, viz., the message given by CREATE when a word already exists with the same name as the current definition attempt. As with hashing names, name collisions can occur in the current system with different names that have more characters than CREATE stores, but fewer than allowed as with a system that stores, say, only the first 3 name characters plus the actual name length.

 

...lee

  • Like 3
Link to comment
Share on other sites

Just when you think you have this Forth thing in the bag ...

 

Over on Reddit Forth, Rick Carlino (the fella that ported Apple ][ Cosmic Conquest to GForth)  found and posted a Github repository that has the Game of Life in APL, which is a one liner in APL.

The APL dialect is written in Forth!

 

If you want to tear your brain open from the inside here is the Github page.

https://github.com/chmykh/apl-life

The readme file has a complete explanation of how APL was implemented in Forth.

 

Anybody know where I can buy a few more IQ points?

 

 

Here is the final program code in APL in Forth. :-o

\ The Game of Life

: life { _ } ←{ ↑ 1 ⍵ ∨ . ∧ 3 4 = + / , -1 0 1 ∘ . ⊖ -1 0 1 ∘ . ⌽ ⊂ ⍵ } ;
 
 
  • Like 2
  • Confused 1
Link to comment
Share on other sites

Over in another thread a comment was made about the difference in speed when accessing VDP RAM versus CPU RAM.

These differences are very large when running Assembly language but I noticed that when you insert an interpreter's overhead the differences become less significant.

I first saw this when I wrote up an adventure game and had all the strings compile into VDP RAM to save program space.

At a glance the game seemed to respond the same as strings in CPU RAM.

 

The little benchmark in the video shows that getting the strings to print out does not make a huge difference in Forth where the interpreter and looping constructs take up significant time and that washes out the time to read the VDP strings. These strings are being read one char at a time from VDP into the Forth top of stack register and then the register is written back to the VDP screen. About the slowest way there is. 

 

 

Next I create identical variables and constants in CPU RAM and VDP ram and stuck them in loops for 10000 iterations.

The difference is only about 50%. 

(To be fair my kernel variables and constants are faster than these because I write them without using CREATE/DOES> but this was the easiest way to do an Apples to Apples comparison) 

 

My conclusion is that putting strings in VDP RAM is a viable use.

Other data types may need evaluation as to whether the speed difference is a problem.  In most cases it is probably not a problem.

 

VARSandCONST-compare.png.95911c3571e6bc6b179cf6d386d60cae.png

 

 

Here is the code for the test.

 

Spoiler

\ Forth DATA in VDP memory

NEEDS ELAPSE FROM DSK1.ELAPSE
NEEDS VHERE  FROM DSK1.VDPMEM  ( VHERE VALLOT  VC,  V, VCOUNT VCREATE )

\ VDP RAM const and var
: VCONST ( n -- ) CREATE  VHERE ,    V,  DOES> @ V@ ;
: VVAR   ( -- )   CREATE  VHERE ,  0 V,  DOES> @ ;

\ make the same construct for vars and const in CPU RAM ( apples to apples)
: CONST           CREATE  HERE ,      , DOES> @  @ ;
: VAR             CREATE  HERE ,    0 , DOES> @ ;

\ compile stack string into VDP memory
: VS,     ( $adr len-- )  VHERE OVER CHAR+  VALLOT VPLACE ;

\ Print a VDP stack string
: VDPTYPE   ( vdp_addr len -- ) BOUNDS ?DO   I VC@ (EMIT)  LOOP ;

\ Compile a VDP string, that types itself
: V."   ( <text> )
        ?COMP                     \ Abort if not compiling
        VHERE                     \ free memory location in VDP RAM
        [CHAR] " PARSE VS,        \ compile <text> into VDP RAM upto quote

       \ Compile this code after the string so it prints itself
        ( vhere) POSTPONE LITERAL
        POSTPONE VCOUNT
        POSTPONE VDPTYPE ; IMMEDIATE

\ TESTS
DECIMAL
VAR X
VAR Y
: INT-TEST
      99  X !
      10000 0
      DO
          X @  Y !     \ transfer x -> y (RAM->RAM)
      LOOP ;

VVAR VX
VVAR VY
: VINT-TEST
      99 VX !
      10000 0
      DO
         VX V@ VY V!   \ transfer vx -> vy (VDP ->VDP )
      LOOP ;

HEX
994A CONST FTI
994A VCONST VTI

DECIMAL
: CONST-TEST  10000 0  DO  FTI DROP LOOP ;
: VCONST-TEST 10000 0  DO  VTI DROP LOOP ;

: TEXT-TEST  PAGE 60 0 DO ." Testing 1 2 3 "  LOOP ;
: VTEXT-TEST PAGE 60 0 DO V." Testing 1 2 3 " LOOP ;

 

 

Edited by TheBF
small mistake in CONST definition
  • Like 5
Link to comment
Share on other sites

After I tested the VDP strings above I started re-working a string library that lives in VDP RAM.

By using the stack string concept (Vaddr,len) there is no penalty to cutting these strings except for the byte fetch operation and the standard Forth word /STRING work just fine even though the addresses are in VDP RAM.

 

Concatenation uses free memory in low RAM as a buffer but writes the resultant string back into VDP RAM in unallocated space. It's a bit awkward but seems fast enough.

 

Here is the code.  I can only promise that it works in V2.69 which is not release yet.

Spoiler

\ VDP STRINGS Forth style stack strings in VDP Memory 31May2022  Brian Fox
\ This saves lots of dictionary space in your program

\ Method:
\ Strings are stored in VDP RAM as counted strings. (1st byte is length)
\ When we invoke the string's name, it returns a VDP stack string.
\ that is a VDP address and a length.
\ With stack strings we don't cut strings to make a sub-strings.
\ We simply adjust the address and length on the Data stack. Very fast!
\
\ Concatenation uses a temp buffer in Low RAM but writes the result back
\ to VDP RAM in unallocated space. The result can be assigned to string variable
\ if that is required.

  NEEDS DUMP   FROM DSK1.TOOLS    \ For debugging
  NEEDS ELAPSE FROM DSK1.ELAPSE   \  for speed testing
  NEEDS VHERE   FROM DSK1.VDPMEM
HERE

DECIMAL
 255 CONSTANT MXLEN   \ 255 bytes is longest string

: HEAP    ( -- addr) H @  ;  \ returns free memory location in LOW RAM

\ ====================================
\ VDP string primitives

\ : VPLACE  ( addr len vaddr -- )  *THIS WORD IS IN THE KERNEL*

\ convert a VDP counted string to a stack string
\ : VCOUNT  ( vdp$adr -- vdpadr len ) DUP 1+ SWAP VC@ ; ( in DSK1.VDPMEM)

\ Print a VDP stack string
: VTYPE   ( vdp_addr len -- ) BOUNDS ?DO   I VC@ EMIT   LOOP ;

\ compile stack string into VDP memory
: VS,     ( $adr len-- )  VHERE OVER CHAR+  VALLOT VPLACE ;

\ read VDP string into temp memory in HEAP, return HEAP address
: VGET$   ( vaddr len -- addr) TUCK  HEAP DUP>R CHAR+ SWAP VREAD  R@ C!  R> ;

\ concat Vstring to CPU string
: V+PLACE ( vaddr len addr -- ) 2DUP 2>R COUNT +  SWAP VREAD 2R> C+! ;

: >VHERE  ( addr len -- Vaddr len) VHERE VPLACE  VHERE VCOUNT ;

\ a bit complicated but it works
: &        ( Vaddr len Vaddr len -- Vaddr len )
           2SWAP VGET$  ( -- heap)             \ get 1st Vstring into HEAP
           V+PLACE                             \ concat 2nd Vstring
           HEAP DUP C@  MXLEN > ABORT" VDP string >255 bytes"
           COUNT >VHERE
;

\ ====================================
\ Replicate TI-BASIC string functions
: VASC    ( vaddr len -- c) DROP VC@ ;
: VLEN    ( vaddr len -- vaddr len len)  DUP ; \ makes a copy of length

: VAL$    ( vaddr len - # ) VGET$ COUNT NUMBER? ?ERR ;

\ These words DO NOT CUT THE STRING; they return a new (Vaddr,len) pair
: VLEFT$  ( vaddr len len' -- vaddr len') NIP ;
: VRIGHT$ ( vaddr len len' -- vaddr len') 1- /STRING ;
: VSEG$   ( vaddr len n1 n2 -- vaddr len) >R VRIGHT$  R> VLEFT$ ;

\ STR$ & CHR$ return a stack string in VDP memory
\ you need to store the result in a VDP string to keep it
: VSTR$    ( n -- Vaddr len) (.) >VHERE ;

\ uses ascii# as an offset into unallocated VDP RAM
\ ie: each char has it's own 1 byte buffer :-)
: VCHR$    ( ascii# -- Vaddr len) DUP VHERE + TUCK VC!  1  ;

\ ===============================
\ Extra string functions
HEX CODE RDROP   05C7 , NEXT, ENDCODE  \ used 4x

DECIMAL
: VTRIM  ( Vadr len char -- Vadr len') \ remove ALL trailing char
         >R
         1-
         BEGIN
            DUP
         WHILE
           2DUP +  VC@ R@ =   \ compare last character
         WHILE
           1-                 \ while char is a match, decrement length
         REPEAT
         THEN
         1+
         RDROP ;

: VSKIP  ( Vadr len char -- Vadr' len') \ remove ALL leading char
         >R
         BEGIN
            DUP
         WHILE
            OVER VC@ R@ =
         WHILE
            1 /STRING
         REPEAT
         THEN
         RDROP ;

: VSCAN (  Vadr len char -- Vadr' len')
        >R     \ remember char
        BEGIN
          DUP
        WHILE ( len<>0)
          OVER VC@ R@ <>
        WHILE ( R@<>char)
          1 /STRING            \ advance to next char address
        REPEAT
        THEN
        RDROP
;

\ : VCLEAN ( V$ len  -- v$' len') BL VSKIP  BL VTRIM ;

\ add a char onto the end of a VDP stack string
: V+CHAR  ( char Vaddr len -- )
          OVER 1- >R     \ compute address of length byte, rpush
          DUP 1+ R> VC!  \ incr len, store new length in VDP
          + VC! ;        \ compute address of end of string, store char

: VPRINT  ( vaddr len -- ) CR VTYPE ;

\ assign text to a VDP stack string in memory
: V$!    ( addr len Vaddr len -- )  DROP 1-  VPLACE ;
: :="    ( vaddr len -- ) DROP 1- [CHAR] " PARSE ROT VPLACE ;

\ Neil Baud's toolbelt compare, modded for VDP RAM
\  0 means adr1 = adr2
\ -1 means adr1 < adr2
\  1 means adr1 > adr2
: VCOMPARE  ( Vadr1 n1 Vadr2 n2 -- -1|0|1 )
    ROT  2DUP - >R            ( a1 a2 n2 n1) ( R: n2-n1)
    MIN                       ( a1 a2 n3)
    BOUNDS
    ?DO                       ( a1)
        VCOUNT  I VC@  -      ( a1 diff)
        DUP IF
            NIP  0< 1 OR      ( -1|1)
            UNLOOP
            RDROP
            EXIT
        THEN                  ( a1 diff)
        DROP                  ( a1)
    LOOP DROP                 ( )
    R> DUP IF  0> 1 OR  THEN   \  2's complement arith.
;

: =V$   ( adr len adr len -- ? ) VCOMPARE 0= ;

: (V")  ( Vaddr -- Vaddr' len)
        POSTPONE LITERAL POSTPONE VCOUNT ; IMMEDIATE

\ compile a VDP string literal into a Forth word.
: V"    ( <text> -- Vaddr len)
       ?COMP VHERE
       [CHAR] " PARSE  VS,
       POSTPONE (V") ; IMMEDIATE

\ ================================
\ VDP string data structures

\ create a VDP buffer of size n
: VBUFFER: ( n -- vaddr)  VHERE CONSTANT  VALLOT ;

: VDIM     ( n -- )
          CREATE
             VHERE
             0 VC,        \ init length to zer0
             SWAP VALLOT  \ allocate space
              ,           \ remember the address
          DOES> @ VCOUNT ;

: V$ARRAY  ( items len -- )  \ CREATE array of fixed length strings
          \ compile time action
           CREATE VHERE ,  2DUP ( len) , ( #) , * VALLOT
          \ runtime action
           DOES>  ( n -- vaddr len)
                  2@      ( -- n vaddr len)
                  ROT *   ( -- vaddr offset)
                  +       ( -- vaddr[n] )
                  VCOUNT ;

HERE SWAP - DECIMAL .

 

 

 

Here is some test code to show how this can be used.

Spoiler

\ VDP STRING LIB DEMO code

\ create string variables. Assignment at compile time
DECIMAL
 80 VDIM A$    A$ :=" This is A$ in VDP RAM :-)"
 80 VDIM B$    B$ :=" This B$ IN VDP RAM"
 80 VDIM C$    C$ :="             1234                  "
 80 VDIM D$
 80 VDIM E$

\ create string literals (cannot change these)
 : Q$       V" The Vquote word creates a string literal in VDP memory" ;
 : DIRTY$   V"                    <<CORE>>                           " ;

\ You can also assign Forth strings to VDP strings with V$!
 S" Now is the time for all good men "    D$ V$!
 S" to come to the aid of their country." E$ V$!

 : PRINT-TEST
         10 0 DO
            A$ VPRINT
            B$ VPRINT
            Q$ VPRINT
         LOOP ;

: .EQ/NE   ( ? --) IF ." EQUAL " ELSE  ." Not equal "  THEN ;

: =TEST     100 0 DO  Q$ Q$     =V$  .EQ/NE  LOOP ;
: <>TEST    100 0 DO  DIRTY$ Q$ =V$  .EQ/NE  LOOP ;

: TRIMTEST  100 0 DO  DIRTY$ BL VTRIM 2DROP  LOOP  DIRTY$ BL VTRIM VPRINT ;
: SKIPTEST  100 0 DO  DIRTY$ BL VSKIP 2DROP  LOOP  DIRTY$ BL VSKIP VPRINT ;

: CLEANTEST 100 0 DO  DIRTY$ BL VSKIP  BL VTRIM 2DROP  LOOP
                      DIRTY$ BL VSKIP  BL VTRIM VPRINT ;

: CONCAT-TEST 100 0 DO  D$ E$ &  2DROP  LOOP  D$ E$ & VPRINT ;

 

 

Edited by TheBF
Changed code: VCOUNT is in LIB file, error fixed in VASC
  • Like 2
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...