Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

3 minutes ago, TheBF said:

I posted an expansion of the code in the Assembly forum since if I confused you then I was making trouble.

The PUSH, macro is for any register.  So in this case I did not PUSH R4 into memory stack. I only "pushed" R0 R1 & R2.

 

I see that, now—and, I am sure you explained it to me before. I just know that I will always need to relearn that fact any time I want to write or understand written Camel99 Forth Assembler code.

 

...lee

Link to comment
Share on other sites

Can you smell the burnt brain cells out there in web space?  ?

 

I have spent way to much time trying different things to marry Forth WORDLISTS to SAM memory in an elegant way.  I am sure it can be done but I struggle with keeping the mental map of all the pointers to pointers to pointer-pointers... :) 

One my challenges with Forth is that I not only want to write the program, I want to write the words that let somebody else write more programs simply. Double responsibility.

 

The context required to maintain the connections between the Forth Interpreter and compiler and arbitrary blocks of code out there in the SAMS card seem way to bulky the way I was doing it.

I made something that worked fine to load up and run the code.  But letting each code page be found in the wordlist search order exceeded my grasp this time. I am missing something and it doesn't seem very elegant.

 

Next Steps:

One of my specifications is I want to be able to load existing library files into SAMS without changes. 

I might have to make all the compiling words deferred and have a Forth version and a SAMS version and vector them all with a single command.

That would let : push the sams bank# onto the return stack and ; could compile code to revert back to the previous bank# before doing EXIT.

 

 

So I have lots things that work but not to my liking including this which I will call CODEX1.  It works like simplified Forth79 vocabularies where each CODEX only links back to Forth.

You can only compile code into the active CODEX using words in the CODEX and/or in Forth.  Not overly practical but you can load up lots of utilities.  You have to manually activate each CODEX by name.

Not very useful yet.

Spoiler

\ CODEX1: SAMS Memory Forth compiling         Jan 4, 2021 B Fox
\ A CODEX is named 4K page in SAMS memory that contains code.
\ Each code page holds its own DP and CONTEXT at the end of the SAMS page.
\ Each code page is like a small vocabulary that links back to Forth.

\ NEEDS DUMP FROM DSK1.TOOLS
NEEDS SAMSCARD FROM DSK1.SAMSFTH

HERE

HEX              E000 CONSTANT CSEG    \ "CODE SEG" window address
CSEG FFC +            CONSTANT PLINKS  \ local memory pointers, end of each bank
4000 CSEG 0B RSHIFT + CONSTANT CREG    \ compute CSEG SAMS register

VARIABLE  TOTAL-AMS
VARIABLE  BANK#
VARIABLE  PAGE#
CREATE HOME  0 , 0 ,         \ place holder for Forth DP & CONTEXT

: CODEX-RESET ( -- ) EF PAGE# !  TOTAL-AMS OFF ;

CODEX-RESET

: NEWPAGE ( - n) PAGE# 1+! PAGE# @  DUP FF > ABORT" No more CODE pages" ;

: CMAP    ( bank# -- )     \ ** bank# must be in low byte**
         DUP BANK# !       \ last used SAMS bank
         SAMSCARD 0SBO     \ turn on the card
        ( bank#) CREG !    \ store bank# in SAMS register
         0SBZ ;            \ turn off card

: AMS-HERE   ( -- addr) PLINKS @ ;
: DICTIONARY ( -- dp context)  DP @ CONTEXT @ @  ;
: RELINK     ( dp context -- ) CONTEXT @ !  DP ! ;
: ACTIVATE   ( bank# -- ) CMAP  PLINKS 2@ RELINK ;
: FAR:       ( -- ) BANK# @ ACTIVATE ;
: LOCAL:     ( -- ) HOME 2@ RELINK ;
: END-LOCAL  ( -- ) DICTIONARY HOME 2!  ;
: BANK-MEM   ( -- n ) PLINKS 2@ DROP  CSEG - ;
: END-SAMS   ( -- )
            DICTIONARY PLINKS 2!  LOCAL:
            AMS-HERE EFFF > ABORT" SAMS page size exceeds 4K"
            BANK-MEM TOTAL-AMS +!
;

: .SAMSCODE   CR ." SAMS CODE: "  TOTAL-AMS @ U. ;

: CODEX: ( -- )
     CREATE
         NEWPAGE >< DUP , CMAP
         CSEG  CONTEXT @ @ PLINKS 2!  \ init local memory pointers in SAMS
     DOES> @ ACTIVATE ; IMMEDIATE

HERE SWAP - DECIMAL .  .( bytes used)

 

 

Demo code

Spoiler

\ CODEX1 tests

CODEX-RESET

\ define some named code pages
CODEX: MISC
CODEX: ASSEMBLER
CODEX: FILES
CODEX: GRAPHICS
CODEX: TOOLS
CODEX: SOUND

\ END-LOCAL remembers where the Forth dictionary ends
\ You must use it you add more code to Forth
END-LOCAL

MISC
DECIMAL
   : STAR    42 EMIT ;
   : STARS   0 ?DO  STAR LOOP ;
   : HI      CR ." Hello world from SAMS memory!" ;
   INCLUDE DSK1.SOUND

END-SAMS

ASSEMBLER
    INCLUDE DSK1.ASM9900
END-SAMS

GRAPHICS
    INCLUDE DSK1.GRAFIX
    INCLUDE DSK1.COLORS
    INCLUDE DSK1.DIRSPRIT
    INCLUDE DSK1.AUTOMOTION
END-SAMS

FILES
    INCLUDE DSK1.DIR
    INCLUDE DSK1.CATALOG
    INCLUDE DSK1.BLOCKS
END-SAMS

TOOLS
    INCLUDE DSK1.TOOLS
    INCLUDE DSK1.ELAPSE
END-SAMS

TEXT 17 7 VWTR

DECIMAL
.SAMSCODE

 

 

  • Thanks 1
Link to comment
Share on other sites

Catching up with TI-BASIC

 

I am reviewing everything for release V2.66 and I have never been happy with how I handled character definitions.

I was also envious of BASIC's ability to handle character definition strings of arbitrary length.  :) 

The secret to that feature is using a text string as the input parameter.

 

It occurred to me that now I have all the pieces needed to make a "CALLCHAR"  that works like TI-BASIC.

I just had to put the pieces together.

 

The pieces used are

  1. VDP memory manager in Forth style that lets you compile numbers into VDP memory sequentially as done in the Forth dictionary
  2. Forth's DIGIT? that converts an ascii char to a number with a conversion success flag (re-written in Assembler in V2.0)
  3. S"  Forth word to define a string literal
     

NEEDS VC, FROM DSK1.VDPMEM

: >NIB  ( char -- n) DIGIT? 0= ABORT" Bad char" ;

: CALLCHAR ( addr len char --)
        BASE @ VP @ 2>R                \ save radix & VDP mem pointer
        HEX
        8* 800 +                       \ compute VDP address of char
        VP !                           \ set VP pointer
        BOUNDS                         \ convert addr,len to addresses
        DO
           I C@ >NIB  4 LSHIFT         \ convert left nibble & shift
           I 1+ C@ >NIB                \ convert right nibble
           OR VC,                      \ OR them, compile into VDP RAM
        2 +LOOP
        2R> VP ! BASE !                \ restore VP and BASE
;

EDIT:  I forgot that I had to convert chars to nibbles and put them together. :) 

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...

I was working on refining the operation of the ED99 editor. 

I never liked the fact that if you are copying records in SAMS memory and you only have one 4K window in memory, as I did, you have to copy the record out to RAM and then write the copy back to the new record.

All this to avoid the the boundary where you want to move the last record of a page to the first record of a new page or vice versa.

 

I recently read a presentation by Chuck Moore and he spoke about using the word BLOCK to deal with memory even when there are no disks on the system.

For those who don't speak FORTH, BLOCK takes an integer argument and returns the address where you can get a "block" of disk space. This is traditionally 1K.

There are typically a number of buffers and BLOCK has a enough smarts to manage the disk and memory like a virtual memory system.

 

Wouldn't it be nice if SAMS memory could be accessed so simply?  Well I took today to figure out a way.

I decided to keep two 4K windows in LOW RAM.  A BLOCK is  4K in size for simplicity.  The code toggles back and forth between the two buffers as you request a SAMS "BLOCK".

If the SAMS page is mapped into a buffer already, it just returns the buffer address.

 

I also provide BUFF0 and BUFF1 to map in pass-thru memory for those cases where you just need regular memory for other stuff.

 

This is not optimal code by any means but it seems to work. I will know for sure when I weave it into a new version of ED99. 

 

Spoiler

\ BLOCK  as a method to manage SAMS pages    Jan 14 2021 Brian Fox

\ NEEDS .S FROM DSK1.TOOLS

HERE

DECIMAL
 24 USER 'R12  \ address of R12 in any Forth workspace

HEX
: SAMSCARD  ( -- ) 1E00 'R12 ! ;   \ select sams card
\ using machine code so we don't need the CRU library
HEX
\ *set the CRU address in 'R12 before using these words*
  CODE 0SBO  ( -- ) 1D00 ,  NEXT, ENDCODE
  CODE 0SBZ  ( -- ) 1E00 ,  NEXT, ENDCODE
  CODE 1SBO  ( -- ) 1D01 ,  NEXT, ENDCODE
  CODE 1SBZ  ( -- ) 1E01 ,  NEXT, ENDCODE

: SAMS-ON   ( -- ) SAMSCARD 1SBO ;  \ enable mapper
: SAMS-OFF  ( -- ) SAMSCARD 1SBZ ;  \ disable mapper

\ * SAMSINI sets card to "pass-through" condition
: SAMSINI
       SAMSCARD          \ select SAMS card
       0SBO              \ turn card on
       0                 \ register value stays on stack
       4000 20           \ register address, # SAMS regs
       BOUNDS ( -- 4100 4000)
       DO
           DUP I !       \ I is reg. address
           I @ OVER <> ABORT" SAMSINI err"
           0100 +        \ next value 0101 for 1MB card, 0100 Classic99
       2 +LOOP
       0SBZ              \ turn off card
       DROP
;

: MAP  ( block# buffer -- )  
         SWAP >< SWAP                \ (fix this with code word)
         0B RSHIFT                   \ divide by 2048
         4000 +                      \ convert to SAMS register address
         SAMSCARD 0SBO               \ enable SAMS card
         !                           \ write block# to SAMS register
         0SBZ ;                      \ turn off SAMS card

\ ==========================================
\ block manager uses handles 0 & 1
DECIMAL
 256 CONSTANT HIGHBLK    \ assumes 1Mb card (256*4K = 1Mb)

VARIABLE USE

\ BUFFER management arrays
HEX
CREATE BLK#S       0 ,    0 ,      \ SAMS page in the buffer
CREATE WINDOWS  2000 , 3000 ,      \ windows in Low CPU RAM

: ]BLK#   ( handle -- addr)   CELLS BLK#S +  ;
: ]BUFFER ( handle -- buffer) CELLS WINDOWS + @ ;

HEX
: BUFF0  ( -- addr) 2000 02 OVER MAP ; \ map in pass-thru memory
: BUFF1  ( -- addr) 3000 03 OVER MAP ;

: BLOCK   ( block# --- addr )
\           HIGHBLK OVER U> ABORT" SAMS page error"    \ optional range test
           0 ]BLK# @ OVER = IF DROP  2000   EXIT THEN  \ mapped in 0?  return buffer
           1 ]BLK# @ OVER = IF DROP  3000   EXIT THEN  \ mapped in 1?  return buffer
         \ page is not already mapped...
           USE @ 1 XOR USE !           \ toggle to USE other buffer
           DUP USE @ ]BLK# !           \ record block# for this handle
               USE @ ]BUFFER TUCK MAP  \ get the buffer address & map in SAMS page
;

HERE SWAP - DECIMAL  CR .  .( bytes)

 

 

  • Like 3
Link to comment
Share on other sites

SAMS BLOCK is even better in CODE :) 

 

I am always impressed with how Forth code maps so well onto 9900 Forth Assembler .

(Gives me more ideas about a Machine Forth system that maps closer to 9900 than Chuck's ideas which were based on his F21 CPU.)

 

Just did a timing test and it looks like this code is 9X faster than the Forth code version. :-o

\ block as SAMS manager
HEX
VARIABLE USE
CREATE BLK#S       0 ,    0 ,      \ SAMS page in the buffer
CREATE WINDOWS  2000 , 3000 ,      \ windows in Low CPU RAM

CODE BLOCK ( bank -- buffer)
          R0 BLK#S LI,     \ handle 0 search
         R0 ** TOS CMP,
          EQ IF,
                TOS 2000 LI,
                NEXT,
          ENDIF,
                R0 INCT,   \ handle 1 search
         R0 ** TOS CMP,
          EQ IF,
                TOS 3000 LI,
                NEXT,
          ENDIF,

           W  0001 LI,
         USE @@  W XOR,
         W  USE @@ MOV,
         W       W ADD,   \ W holds offset
     TOS BLK#S (W) MOV,   \ store the bank#
    WINDOWS (W) R1 MOV,   \ get the window to use
          R1    0B SRL,   \ divide by 2048
          R1  4000 AI,    \ convert to SAMS register address
          R12 1E00 LI,    \ cru address of SAMS
                 0 SBO,   \ SAMS card on
              TOS  SWPB,  \ swap bytes on bank value
         TOS R1 ** MOV,   \ load bank into register
                 0 SBZ,   \ SAMS card off
   WINDOWS (W) TOS MOV,   \ return buffer on TOS
                   NEXT,
ENDCODE

Edit: Did the SWPB too early in 1st version

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

I am really enjoying using the BLOCK code for SAMS management.  I am making some major changes to ED99 and IT really has simplified everything.

 

Here is my new definition for a temporary buffer: :) 

: HEAP  ( -- addr)  2 BLOCK ;

It's so seamless. It just swaps out the SAMS page that was last used and gives me a 4K block of pass thru memory.

 

I have to use some 32bit intermediate math to compute a record but it's in the kernel so no worries.


VARIABLE BLK
: ]RECORD ( n -- addr)  RECSIZE UM* 4K UM/MOD 1STBLK + DUP BLK ! BLOCK + ;

1STBLK is just an offset into the 255 available blocks computed from a variable that is the file# we are editing.

I record the block in use in case I might need it but so far I have not, since the editor works mostly at the line level.

 

I built a clipboard by doing the same thing but using a "1stblock" that is above the last block used for text.

DECIMAL
\ clipline returns the string at top of clipstack
\ clip board uses blocks 161..193
: ]CLIP    ( rec# -- addr) RECSIZE UM* 4K UM/MOD 161 + BLOCK + ;

Since there are two 4K buffers copying a line of text to the clipboard is just:


: LINE2CLIP ( line# -- )
      ]RECORD CLIPLINE COPY-REC 
      #CLIPS 1+!
;

It seems to be coming together much easier now.

 

I hope to have new version out this week. 

Working on adding the ability to mark text lines (with highlighting) and cutting them out to clipboard and pasting them back in.

Old version can copy one line at a time or copy the entire file.

 

 

  • Like 3
Link to comment
Share on other sites

I am working hard on getting a new release of ED99.

It will have some new features like hi-lighting lines for copy/delete, 1000 lines per file and paste now can insert lines anywhere in a file.

I have limited it to 5 files open at once to save some room in SAMS memory for future development. It is still record based and very wasteful of the memory.

 

Wouldn't it be nice to be able to delete files that you no longer need? :)

Tested it here on my TI-99.

(It doesn't work on Classic99 of course but will be great for working on real iron)

 

 

  • Like 2
Link to comment
Share on other sites

Using Marker to Save Space

 

Something I had never considered before was using the ANS word MARKER and executing a marker during compilation.

Typically I use it to remove a section of a program that I am debugging so I can recompile the troublesome section and re-test.

But in reviewing a library to do inverted fonts I realized I don't need the code that makes the inverted font after everything is written to VDP RAM.

So why not remove the code after it has served its purpose?

 

I took Lee's advice from some months ago to simplify this code for my next release.

Since I include MARKER at startup, because I was finding it so handy, this code now takes less space.

Only the words that the programmer needs remain in the dictionary.

 

(Of course if you needed to be loading new fonts in your program you might want to keep INVERTFONT so you can always comment the MARKER and /REMOVE lines)

\ HILITE.FTH   create reverse color characters and removes itself
\ INCLUDE DSK1.TOOLS

MARKER /REMOVE             \ put a "bookmark in the dictionary"

NEEDS MALLOC  FROM DSK1.MALLOC
HEX
: ]PDT   ( char# -- 'pdt[n] ) 8* 800 + ; \ VDP pattern Descriptor table

400 CONSTANT 1K

: INVERTFONT ( -- )
        1K MALLOC >R                \ get a 1K buffer
        0 ]PDT R@  1K VREAD         \ copy VDP patterns to RAM
         R@  1K  BOUNDS             \ loop thru 1K byte by byte
         DO
           I DUP C@ INVERT SWAP C!  \ invert char pattern
         LOOP
         R@  80 ]PDT  1K VWRITE     \ write RAM back to VDP upper charset
         R> MFREE ;                 \ release the memory

INVERTFONT    ( change the VDP memory)
 /REMOVE      ( remove the above code from the Forth dictionary)

\ *permanent routines*
\ Type a string with reversed colors
  : HITYPE ( addr len --)   BOUNDS DO   I  C@ 80 OR (EMIT)   LOOP ;

\ These operate on the VDP screen only. The original strings are not changed
  : HILITE  ( Vaddr len --) BOUNDS DO   I VC@ 80 OR  I VC!   LOOP ;
  : NORMAL  ( Vaddr len --) BOUNDS DO   I VC@ 7F AND I VC!   LOOP ;
  : REVERSE ( Vaddr len --) BOUNDS DO   I VC@ 80 XOR I VC!   LOOP ;

 

  • Like 3
Link to comment
Share on other sites

I don't know if any of the Forth programmers have been following some posts in the TIPI development area so want to point out an observation.

 

I found one program, and likely it is two, McCann's The Printer's Apprentice and likely The Geometer's Apprentice that may have an issue should the user decide to print to PI.PIO if they have a TIPI.  Just making a note here for others not knowing how the forth definitions are written if a user wanted to print to the PI.PIO device.

 

The TIPI and PI require a PAB for OPEN, WRITE, and then CLOSE to properly render to PDF. I have come across Myart and The Printer's Apprentice that just WRITE without an OPEN an CLOSE.  That's fine for the RS232 card, but will lock up the PI and the computer requiring a PI reboot if the OPEN and CLOSE are not implemented.

 

Beery

 

 

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

I had some communications with atrax27407   today 

 

He mentioned that Wycove Forth had a 40 column editor that used Forth blocks but mapped them onto the the TI-99 40 col. display.

I had started something like that last year and abandoned it because I was not sure it was of any real use.

My original used only VDP RAM and so could only hold 9K files. It saved and loaded DV80 files.

I got back into it and this version uses a block file so text can be the size of the disk.

 

The "innovation" here, if you can call it that, is the VDP RAM page is the actual buffer you edit. It seems to respond quicker because the editor never leaves VDP RAM.

When you save,  the VDP screen just writes the entire screen to a disk block.

 

I created a library called "SCREENS" some time ago to let me create multiple screens in VDP RAM.

I had never used it but it lets me give the editor it's own "window".

 

I find it is pretty nice to to have 24 lines of 40 chars. Not as restricting as I thought it might be.

To put information on the screen I just clear the top the line to display info and then re-read the disk block data back onto the screen.

It does the trick.

 

I am tempted to add an undo. I can keep the extra data in another VDP page I think...  :) 

 

Spoiler

( SCREEN EDITOR in VDP buffer  )
( Jan 26 2021 Brian Fox)
NEEDS DUMP      FROM DSK1.TOOLS
NEEDS CASE      FROM DSK1.CASE
NEEDS RKEY      FROM DSK1.RKEY
NEEDS -TRAILING FROM DSK1.TRAILING
NEEDS FAM       FROM DSK1.ANSFILES
NEEDS MALLOC    FROM DSK1.MALLOC
NEEDS SCR4      FROM DSK1.SCREENS
NEEDS BLOCK     FROM DSK1.BLOCKS
HERE
\ variables ...
VARIABLE INSERTING
VARIABLE TOPLINE
VARIABLE OFFSET
VARIABLE SCR
VARIABLE ^LSTK

C/L@ 1- CONSTANT WIDTH

\ 16 line, circular line stack
\ allocated in HEAP at compile time
 HEX
 C/L@ 10 * MALLOC CONSTANT LINESTK

 0F CONSTANT LMASK

: LSTK++  ( )
  ^LSTK DUP @ 1+  LMASK AND SWAP ! ;

: LSTK--  ( )
  ^LSTK DUP @ 1-  LMASK AND SWAP ! ;

: STKLINE ( -- addr)
  ^LSTK @ C/L@ * LINESTK +  ;

\ ==================================
\ VDP screens for Forth & editor
\ Vpage fg  bg
\ ----- --  --
  0   1   7 SCREEN: SCR0  \ forth
  4   1   3 SCREEN: SCR4  \ editor

: LOADSCR ( n -- )
   DUP SCR ! BLOCK VPG @ 3C0 VWRITE ;

: SAVESCR ( n -- )
   VPG @ SWAP BLOCK 3C0 VREAD UPDATE ;

HEX
1F CONSTANT ULINE   1E CONSTANT BOX
: GREEN   1C 7 VWTR ;
: LTGRN   13 7 VWTR ;
: CYAN    17 7 VWTR ;
: CLIP     ROT MIN MAX ;

: BETWEEN ( n lo hi -- ?) 1+ WITHIN ;
: ALPHA?  ( -- ?) BL [CHAR] ~ BETWEEN ;
: VBLANK  ( vaddr n --) BL VFILL ;
: GETXY  ( -- col row) VROW 2@ ;
: ERASELN ( vaddr -- ) C/L@ VBLANK ;
CREATE FORTHXY  0 , 0 ,
CREATE EDITXY   0 , 0 ,
EDITXY       CONSTANT EROW
EDITXY CELL+ CONSTANT ECOL

DECIMAL
: EMATCH  GETXY EDITXY 2! ;

: CUP    VROW @ 0 > IF VROW 1-!
         ELSE HONK THEN EMATCH ;

: CDOWN  VROW @ 23 < IF VROW 1+!
         ELSE HONK THEN EMATCH ;

: CRIGHT VCOL @ 39 < IF VCOL 1+!
         ELSE HONK THEN EMATCH ;

: CLEFT  VCOL @ 0 > IF VCOL 1-!
         ELSE HONK THEN EMATCH ;

: NEWLINE ( ) CDOWN  VCOL OFF ;

DECIMAL
: INS/DEL  ( ) \ toggle mode
    INSERTING DUP @ -1 XOR SWAP !
    INSERTING @
    IF   BOX CURS !
    ELSE ULINE CURS !
    THEN ;

HEX \ vdp BLOCK creation
400 CONSTANT 1K

: VLINE ( n -- vaddr) C/L@ * VPG @ + ;
\ return vdp address of current row
: ELINE ( -- VDPaddr) EROW @ VLINE ;

: LNPUSH  ( vaddr -- )
  STKLINE C/L@ VREAD  LSTK++ ;

: LNPOP  ( vaddr -- )
  LSTK-- STKLINE SWAP C/L@ VWRITE ;

: RIGHTSIDE ( -- VDPaddr len)
  ELINE C/L@  VCOL @ /STRING ;

: LEFTSIDE  ( -- VDPaddr len)
  ELINE VCOL @ 1+ ;

\ copy vdp string to RAM address
: VCOPY ( vaddr len addr --)
  SWAP VREAD ;

\ text manipulation
: DELCHAR    ( -- )
  RIGHTSIDE 1 /STRING TUCK
  PAD VCOPY
  BL ELINE C/L@ + VC!
  PAD VPOS ROT VWRITE ;

: PUSHRIGHT ( -- )
  RIGHTSIDE TUCK
  PAD VCOPY
  BL VPUT
  PAD VPOS 1+ ROT 1- VWRITE ;

HEX
: FORTH ( -- ) \ goto forth console
  GETXY EDITXY 2!
  SCR0  ( goto Forth VDP screen)
  FORTHXY 2@ AT-XY
  ULINE CURS !
  ABORT
;

HEX
400 CONSTANT 1K

: TINK     ( -- )
  80 SND! 2 SND!  94 SND! 25 MS
  9F SND! ;

: PROMPT:  0 0 AT-XY VPOS ERASELN TINK ;
: .PAGE#   PROMPT: ." SCR# " SCR @ . ;

: PGDWN ( )
  GETXY 2>R
  SCR 1+! .PAGE#
  SCR @ LOADSCR
  2R> AT-XY ;

: PGUP   ( )
  GETXY 2>R
  SCR @ 1- 0 MAX SCR ! .PAGE#
  SCR @ LOADSCR
  2R> AT-XY ;

: LASTLN   L/SCR 1- VLINE ;

: -BLANKS ( vaddr -- addr len )
  1- BEGIN 2DUP + VC@
  BL = OVER 0> AND
  WHILE 1-  REPEAT   ;

: EMPTY? ( vaddr -- ?)
  C/L@ -BLANKS NIP 0= ;

: PULLUP ( )
  L/SCR EROW @
  DO   I 1+ VLINE C/L@ PAD VCOPY
      PAD I VLINE C/L@ VWRITE
  LOOP
  LASTLN ERASELN ;

: PUSHDN
      EROW @ L/SCR 1-
      DO  I 1- VLINE C/L@ PAD VCOPY
          PAD I VLINE C/L@ VWRITE
      -1 +LOOP ;

: BACKSPACE
  VCOL @ 0>
  IF CLEFT DELCHAR
  ELSE HONK
  THEN ;

: LCOPY ( ) ELINE LNPUSH ;
: CUT   ( ) ELINE DUP LNPUSH  ERASELN ;
: YANK  ( ) CUT PULLUP ;

: DELETE
  ELINE EMPTY?  ECOL @ 0= AND
  IF   PULLUP
  THEN DELCHAR ;

: PASTE ( )
  LASTLN EMPTY?
  IF PUSHDN   ELINE LNPOP
  ELSE HONK  THEN ;

: LINSRT ( )
  LASTLN EMPTY?
  IF PUSHDN   ELINE ERASELN
  ELSE HONK  THEN ;

: Y/N?  ( -- ?)
  KEY  DUP [CHAR] Y =
  OVER [CHAR] y = OR ;

: PURGE
  PROMPT: ." Purge this block?"
  Y/N? IF
     SCR @ BLOCK B/BUF 20 FILL
     UPDATE SCR @ LOADSCR
  THEN ;

: INFO
  GETXY 2>R
  .PAGE# 8 SPACES ACTIVE COUNT TYPE
  KEY DROP
  SCR @ LOADSCR
  2R> AT-XY  ;

: SAVING
  SCR @ SAVESCR
  GETXY 2>R
  PROMPT: ." Saved block..."
  300 MS
  SCR @ LOADSCR
  2R> AT-XY ;

HEX
: KEYHANDLER ( char -- )
  CASE
    02 OF PGDWN    ENDOF \ F4
    03 OF DELETE   ENDOF \ F1
    04 OF INS/DEL  ENDOF
    06 OF LINSRT   ENDOF \ F8
    07 OF ELINE ERASELN ENDOF
    0C OF PGUP     ENDOF \ F6
    0D OF NEWLINE  ENDOF \ Enter
\ cursor
    08 OF CLEFT       ENDOF
    09 OF CRIGHT      ENDOF
    0A OF CDOWN       ENDOF
    0B OF CUP         ENDOF
    0E OF 0 LOADSCR   ENDOF
\
    90 OF PURGE       ENDOF ( ^P)
    93 OF SAVING ENDOF ( ^S)
    95 OF VCOL OFF    ENDOF
    88 OF BACKSPACE   ENDOF
    89 OF INFO        ENDOF
    83 OF LCOPY       ENDOF
    96 OF PASTE       ENDOF ( ^V)
    99 OF YANK        ENDOF ( ^Y)
\   9A OF UNDO         ENDOF (^Z)
    0F OF FLUSH FORTH ENDOF ( ESC)
          HONK  ( unknown key)
  ENDCASE ;

: SETUP
  VMODE @ 2 <> IF TEXT THEN
  GETXY FORTHXY 2!
  SCR4  PAGE  LOADSCR
  INSERTING OFF
  ULINE CURS !
  0 0 2DUP EDITXY 2! AT-XY ;

DECIMAL
: EDIT ( scr# -- )
  SETUP
  BEGIN
      RKEY DUP ALPHA?
      IF INSERTING @
         IF   PUSHRIGHT
         THEN VPUT UPDATE CRIGHT
      ELSE KEYHANDLER
           RKEY? DROP
      THEN
  AGAIN ;

: USE    BL PARSE-WORD  OPEN-BLOCKS ;
: REOPEN ACTIVE COUNT OPEN-BLOCKS ;
HERE SWAP - . .( bytes used)

 

 

 

 

 

 

 

 

  • Like 2
Link to comment
Share on other sites

I found myself thinking about Tursi's cool BASIC program that plotted a sinusoidal wave without using bit map mode.

It occurred to me that I had created a pretty efficient bit array system a while back.

Could I use a bit array and map it onto a character pattern to plot in the 8x8 character matrix.

Yes I could.

 

Here is a demo program that just plots lines pixel by pixel in a character at different speeds.

I should be able to plot 127 characters to plot functions with this thing.

 

Spoiler

\ PLOT to 8x8 character matrix                  Jan 27, 2021 Brian Fox

\ BOOLEAN array creator and access words               Brian Fox  2018
\ Tested with  CAMEL99 Forth 16 bits, Gforth 32 bits, iForth 64 bits
\ July 2019, removed Rstack juggling BSET, BRST, BTOG.
\ Relaced with OVER. 20% faster

NEEDS DUMP     FROM DSK1.TOOLS
NEEDS CHARDEF  FROM DSK1.GRAFIX
NEEDS ?BREAK   FROM DSK1.BREAK

MARKER REMOVE

HEX
\ calculations based on CELL size
                   8 CONSTANT BITS/BYTE
 1 CELLS BITS/BYTE * CONSTANT BITS/CELL

\ create bit array & erase memory area for 'n' bits
: BITS:      ( n -- )
      CREATE  BITS/BYTE /MOD SWAP >R   \ calc memory size
              HERE OVER 0 FILL         \ erase the memory
              R> CELLS + 2+ ALLOT  ;   \ allocate the memory

\ compute bit# in a cell & cell address in memory
\ usage:  42 MYARRAY BITFLD
: BITFLD  ( bit# bits[] -- bit#' addr) SWAP BITS/CELL /MOD CELLS ROT + ;

\ convert bit# to a bit mask
: BITMASK ( bit# -- n )  0001 SWAP LSHIFT >< ; \ 9900 needs a byte swap

\ API
: BIT@ ( bit# bits[] -- ?) BITFLD @ SWAP RSHIFT 0001 AND ;  \ return 1 bit
: BSET ( bit# bits[] -- )  BITFLD SWAP BITMASK        OVER @  OR SWAP ! ;
: BRST ( bit# bits[] -- )  BITFLD SWAP BITMASK INVERT OVER @ AND SWAP ! ;
: BTOG ( bit# bits[] -- )  BITFLD SWAP BITMASK        OVER @ XOR SWAP ! ;

DECIMAL
64 BITS: MATRIX

HEX
: ERASE   0 FILL ;
: CLR    ( -- ) MATRIX 8 ERASE  MATRIX 80 CHARDEF ;

: CALCXY ( col row -- )  8* +  MATRIX BSET ;

: PLOT  ( col row --) CALCXY   MATRIX 80 CHARDEF ;

\ Test code
DECIMAL
CREATE DLY  20 ,
: BOT2TOP CLR
         8 0 DO
           8 0 DO
                  I J PLOT
                  DLY @ MS
               LOOP
         LOOP ;

: TOP2BOT
       CLR
       0 7 DO
       0 7 DO
              I J PLOT
              DLY @ MS
        -1 +LOOP
          ?BREAK
      -1 +LOOP ;

: R2LEFT  CLR
         8 0 DO
           8 0 DO
                  J I PLOT
                  DLY @ MS
               LOOP
               ?BREAK
         LOOP ;


: L2RIGHT  CLR
         0 7  DO
           0 7  DO
                  J I PLOT
                  DLY @ MS
              -1 +LOOP
              ?BREAK
         -1 +LOOP ;


: RUN  BEGIN
         BOT2TOP  R2LEFT
         TOP2BOT  L2RIGHT
      AGAIN ;

HEX 80 SET# 2 3 COLOR
PAGE
CR CR 80 EMIT

 

 

  • Like 1
Link to comment
Share on other sites

On 1/26/2021 at 5:17 PM, BeeryMiller said:

I don't know if any of the Forth programmers have been following some posts in the TIPI development area so want to point out an observation.

 

I found one program, and likely it is two, McCann's The Printer's Apprentice and likely The Geometer's Apprentice that may have an issue should the user decide to print to PI.PIO if they have a TIPI.  Just making a note here for others not knowing how the forth definitions are written if a user wanted to print to the PI.PIO device.

 

The TIPI and PI require a PAB for OPEN, WRITE, and then CLOSE to properly render to PDF. I have come across Myart and The Printer's Apprentice that just WRITE without an OPEN an CLOSE.  That's fine for the RS232 card, but will lock up the PI and the computer requiring a PI reboot if the OPEN and CLOSE are not implemented.

 

Beery

 

 

It would never occur to me to not open and close files. :) 

(Although I did write my own direct driver for RS232 so guess I shouldn't speak so quickly)

Don't have TIPI yet but one day this will be useful.

Thanks Beery

Link to comment
Share on other sites

For some reason I thought it would be tricky to make program loader for E/A 5 binaries.

Turns out I had all the tools I needed and I just had to glue them together.

( addr len) below means a stack string pair.

 

So usage is:   S" DSK1.MYFILE"  LOADER 

LOADER will load files with the incremented last character names like the E/A Cartridge Option 5.

( I have not tested that yet but I looks like it should work) ;) 

 

\ LOADER.FTH   E/A file loader for CAMEL99 Forth      Brian Fox jAN 28 2021

MARKER /LOADER

NEEDS LOAD-FILE  FROM DSK1.LOADSAVE

: LASTCHAR++  ( Caddr --) COUNT 1- +  1 SWAP C+! ;
: FIELD       ( n -- Vaddr) VP @ SWAP CELLS + ;

: BLOAD  ( addr len -- ?)
           VP @ 2000 13 LOAD-FILE   \ VP default address is VDP>1000
           3 FIELD         \ -- codestart
           2 FIELD V@      \ -- codestart addr )
           1 FIELD V@      \ -- codestart addr size)
           VREAD           \ read VDP RAM to CPU RAM
           0 FIELD V@      \ return the multi-file flag
;

: LOADER  ( addr len -- )
  BEGIN  2DUP BLOAD  WHILE LASTCHAR++  REPEAT 2DROP ;

 

For anyone who might want to use this idea for FbForth or Turbo Forth beware of V@ here.

In Camel99 Forth V@ reads 2 bytes (integer)  like '@' does for CPU RAM.

 

This code is not optimal but it works in Turbo Forth.


HEX
: VC@    V@ ;  \ rename old V@ 
: V@     DUP VC@ 100 * OVER 1+  VC@ OR ; 

 

 

 

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

I went down the rabbit hole today to see if I could squeak a little more speed out of my VDP driver.

My original goal was to write most of the VDP driver in Forth.  I did that but that is not optimal for speed of course however it is amazing to me how assembly language can start to bloat out your code as well.

It's just a few instructions here and few instructions there but they add up.  And factoring out sub-routines is less space efficient that Forth because each call is 4 bytes vs 2 in Forth. And that assumes that you don't need to nest your subroutines!

 

So the solution that made the most difference was to create a new code word CPUT that computes the VDP address of the cursor, writes the character, compares the column to EOL and return a flag.

That allowed me to keep my IF statements in Forth and to call the Forth CR word, but everything else is code.

I also added a code word called (CR) which resets the column variable and increments the ROW and also returns the ROW so a Forth IF statement can do a SCROLL or not. (CR) is less important but I was in the code so there it is.

 

It all got pretty tidy after this:

: CR     ( -- )     (CR) L/SCR = IF  SCROLL  THEN  ;
: (EMIT) ( char -- ) CPUT IF  CR  THEN ;

 

It reduced my testing benchmark, the Sevens Problem, from 1:05.88 mins  to  1:01.10,  so a nice improvement, but it also added 40 bytes to the kernel which is now 8166 bytes.

For the non-Forthers out there it is hard to believe but you can make programs smaller using Forth versus all Assembler when you use each for what they're best at.

Sounds crazy but is true.

 

I could not have made this fit if I had not reduced the kernel size earlier with the simplified ISO style, branching and loop code.

 

CPUT could be made smaller if I make a sub-routine for the screen write portion of the code but that would require more overhead so I think I am done with this.

CODE: CPUT ( char -- ?)  \ put a char at cursor position, return eol flag
            TOS R2 MOV,
            R1         STWP,    \ workspace is USER area base address
            32 (R1) R3  MOV,    \ vrow->r3
            2E (R1) R3  MPY,    \ vrow*c/l->tos
            34 (R1) TOS ADD,    \ add vcol
            VPG @@  TOS ADD,    \ add video page address
            TOS R0 MOV,
            WMODE @@ BL,
            R2 SWPB,
            R2 VDPWD @@ MOV,   \ write char to screen
            2 LIMI,
            TOS CLR,
            34 (R1)  INC,       \ bump VCOL
            34 (R1)  2E (R1) CMP,  \ compare VCOL = C/L
            EQ IF,
                TOS SETO,       \ set true flag
            ENDIF,
            NEXT,
           END-CODE

CODE: (CR) ( -- n)   \ inc VROW , return value
           TOS PUSH,
           R1 STWP,
           34 (R1) CLR,     \ VCOL OFF
           32 (R1) INC,     \ VROW 1+!
           32 (R1) TOS MOV,
           NEXT,
           END-CODE

*WMODE sub-routine disables interrupts, we turn 'em back on when we finish touching the VDP.

 

 

 

 

FASTER SEVENS.png

  • Like 4
Link to comment
Share on other sites

Camel99 2.66

 

Here is the newest version of Camel99 Forth

This changes are really about making a kernel that supports the modern Forth idea of WORDLISTs. This means there is a "context" array that hold 8 wordlists plus a "root' wordlist.

Of course we needed to add "current' variable as well to control which wordlist will get newly compiled code.

 

A few other changes of note:

  • New VDP driver that is between 8% to 14% faster depending on how I benchmark it
  • WORDLISTS lib file lets you make separate name spaces either as Forth 2012 WORDLISTS or as traditional vocabularies. I needed these to create a cross-compiler on top of Camel Forth
    • New words:
      • ORDER  show the search order
      • ONLY   resets order to just ROOT wordlist
      • ALSO  duplicates the first wordlist in the search order 
      • DEFINITIONS  makes the first wordlist in the search order "current" meaning new words compile into that wordlist
      • FORTH   primary system wordlist with the system words in it.  
      • PREVIOUS  return context vocabulary/wordlist to the 2nd wordlist in the search order
      • ROOT   name of the 1st wordlist in the search order
  • MARKER, FORGET, things that touch the dictionary are not compatible with older versions.
     
  • ASM9900 Forth assembler has been streamlined internally
  • Changed the start file to put a TI logo on the screen interpretively to save space. (cute)
  • BEGIN WHILE loops are now in ANS/ISO form so you can have multiple WHILE statements in one loop construct. Really!. :)  It actually is handy for string functions.
  • SAVESYS lets you save the entire system as an E/A5 program. This means you can build your own Forth with the libraries you want, save everything and have them start up immediately.
  • I have changed the default CALLCHAR word to operate more like TI BASIC with <string>,<char> argument order. The string can also be 64 chars long.
  • There is a SBLOCK library that uses Low RAM as buffers space for SAMS pages that swap in automagically with one command.   ( n) BLOCK where n is between HEX 0 and FF and it returns the buffer address.
  • JOYST is a short Assembler word available in a lib file
  • Repeating key word RKEY lib has been made more stable. 
  • The Forth 2012 word SEARCH is available in a lib file. This allows string searches in large memory blocks. 
  • The FONT0230 that loads at start up now has chars for the TI Logo for 40 column  and 32 col mode. It also is pre-built with inverted characters in the 8 bit charset range.
    • See: DSK2.FONT230,SRC  to see how the new font was built from source code.
  • The lib file HILITE lets you use the inverted characters in your programs.
  • XAM9900 is cross assembler that lets you assemble a stand alone ASM program in memory at an "ORG". It will be part of a cross-compiler in future.

In the zip file:

DSK1  is the CAMEL266 E/A5 program and all the library files. 

DSK2 is about editors, the FOXSHELL program and with the source file FSHELL,FTH and fonts

DSK3 is the same. Demo files for examples of how the system is used.

 

I appreciate all feedback since I don't actually have the time to test everything to the ground.

Beat me up and I will work on making it better.

 

What I owe you:

New manual for this version

Finished glossary of words. (I need an administrative assistant) :)

Updated document of library files

 

I have one big issue for myself: I cannot get this version to compile a version for Super Cart.  ?

Homemade tools, nobody to complain to...

 

Terribly sorry.  I tested this on real iron and then I must have re-built and not tested.

This zip file has the CAMEL266 kernel program taken from the floppy drive right after I booted it.

 

Note to self: Always test on real iron. :) 

 

 

 

CAMEL.266.ZIP

  • Like 2
Link to comment
Share on other sites

1 hour ago, atrax27407 said:

I assume that CAMEL99 is supposed to boot as an E/A5 file by typing in: DSK1.CAMEL266 at the prompt. However, there is a problem in MAME - all I get is a blank, green screen. 

Aha!

 

I thought I had it running on real iron.  I will do another transfer and see what's going on.

 

Apologies.

Link to comment
Share on other sites

On 2/9/2021 at 10:24 PM, TheBF said:

Aha!

 

I thought I had it running on real iron.  I will do another transfer and see what's going on.

 

Apologies.

I believe the problem above was that I did not convert the program file to TI Files format.

I really must change my cross-compiler to do provide that file format option.

More work for me.

 

Link to comment
Share on other sites

The discussion that broke out over at Erik's PEB comparing Camel99, FbForth and Turbo Forth in forced me to re-visited how I implemented my DO LOOP.

I always wondered why it sucked.  :) 

 

I was able to get the time down to 1:46.03 (about 8% faster) on the Fibonacci benchmark. 

Earlier when I barely knew what I was doing, I had made decisions to optimize for space because I could barely get a working kernel squashed into 8K. 

Lately I have found ways to save space such that I had about 80 bytes available so this was an easy trade-off.

 

The benchmark that ran in 1:55 on V2.66:

DECIMAL
: FIB2
  0 1 ROT 0 DO
    OVER + SWAP
  LOOP DROP ;
: FIB2-BENCH 1000 0 DO
    I FIB2 DROP
  LOOP ;
  
  1. Reversed the logic in <LOOP> so that most of the time I don't take the code branch but fall through to Forth branch back. This removed a jump.
    (Seems obvious now)  :)
  2. Used INLINE NEXT code rather than branching through a register to code in the 16 bit PAD. This surprised me. Running NEXT code inline in wait-state memory is faster.  This uses 6 extra bytes for the 3 instructions but for such a commonly used structure I will keep it.
CODE: <LOOP>
             *RP INC,           \ increment loop
@@2:          NO IF,            \ if overflow then EXIT loop
                 *IP IP ADD,    \ jump back
                 ILNEXT,        \ faster INLINE NEXT macro
              ENDIF,
              IP INCT,          \ move past (LOOP)'s in-line parameter
              RP  4 ADDI,       \ collapse rstack frame
              NEXT,     
              END-CODE

Using Inline NEXT on exiting the loop did not improve the benchmark time.

 

 

For the curious I also have code that I tested putting the DO LOOP  limit and index in registers and another version with only the index in a register and the limit remaining on the return stack.

 

The difference in time between 2 registers and 1 registers is barely measurable so that's a non-starter.

Putting the index in R15 reduced the benchmark time to  1:43.76  or about another 2% improvement so not really worth in exchange for fast multi-tasking IMHO.

 

CODE: <LOOP>
              R15 INC,          \ increment loop
@@2:          NO IF,            \ if overflow then EXIT loop
                 *IP IP ADD,    \ jump back
                 ILNEXT,        \ faster INLINE NEXT macro
              ENDIF,
              IP INCT,          \ move past (LOOP)'s in-line parameter
              R15 RPOP,         \ restore old R15
              RP INCT,          \ remove limit from rstack 
              NEXT,
              END-CODE

Thanks to Lee for doing the comparisons.

 

 

 

  • 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...