Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

I have something that seems useful now although some code that I have in my libraries breaks when loaded in SAMS.

Need to understand why.

This version runs faster because MAPPER is now smart and does not map in a SAMS page if it is already in memory.

I will do a benchmark comparison between CPU RAM and SAMS memory.

 

Here is the code 

Spoiler

\ Code in SAMS memory based on concept in TurboForth by Mark Wills
\ Ported to Camel99 Forth  Oct 7, 2021

\ Changes from original:
\ Remove bank stack.  Used return stack for bank# storage
\ Removed BANKS word. Changed to preset DP array
\ CMAP is a fast sub-routine for mapping SAMS pages F0..FF
\      CMAP Remembers the last bank that was paged in variable LASTBNK
\ Changed to compile a far "colon" definition to reduce dictionary overhead

\ Oct 7, 2021:  Compares input to CMAP to LAstbnk and
\               does nothing if page is still in memory

\ FAR word data structure:
\   CELL: link
\   BYTE: immediate field
\   BYTE: name length
\   BYTES: <....>
\   CELL:  code field
\   CELL:  DATA field #1 , bank#
\   CELL:  DATA field #2 , SAMS code field address


INCLUDE DSK1.MARKER
INCLUDE DSK1.VALUES
\ INCLUDE DSK1.TOOLS
 INCLUDE DSK1.ASM9900

HERE
F0 VALUE _1STBANK
FF VALUE _MAXBANK

\ SAMS memory management for code
HEX              3000 CONSTANT CSEG      \ code seg in CPU RAM
4000 CSEG 0B RSHIFT + CONSTANT CREG      \ compute CSEG SAMS register
CSEG 0C RSHIFT        CONSTANT PASSTHRU  \ default page for CSEG

DECIMAL
CREATE []DP  \ DP for 0 .. 15 pages of SAMS
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,

HEX
CODE ]DP ( bank# -- addr )
     TOS _1STBANK NEGATE  AI,  \ remove first bank offset to index []DP
     TOS TOS ADD,
     TOS []DP AI,
     NEXT,
ENDCODE

 VARIABLE  SAVHERE   \ temp holder for RAM Dictionary pointer
 VARIABLE  BANK#     \ active SAMS bank# for compiling
 VARIABLE  LASTBNK   \ last bank# passed to MAPPER

CODE BANK#@   ( -- bank#)  \ Used 8 times so this saves space and is fast
      TOS PUSH,
      BANK# @@ TOS MOV,
      NEXT,
ENDCODE

HEX
CREATE MAPPER ( R1: 0 .. 16 )  \ smart MAPPER
      R1 LASTBNK @@ CMP,  \ already mapped?
      NE IF,
         R1 LASTBNK @@ MOV,  \ update the last bank used
         R1 SWPB,          \ swap bytes
         R12 1E00 LI,      \ DO CMAP
         0 SBO,            \ turn on the card
         R1 CREG @@ MOV,   \ restore bank# from return stack
         0 SBZ,            \ turn off card
      ENDIF,
      RT,

CODE CMAP  ( bank# --) \ Forth word to map SAMS pages
      TOS R1 MOV,
      MAPPER @@ BL,
      TOS POP,
      NEXT,
      ENDCODE

CODE GOTO  ( addr -- ) *IP IP MOV,  NEXT, ENDCODE

CREATE FARCOL   \ run time executor for SAMS colon words.
     IP RPUSH,
     W IP MOV,
     RP DECT,
     LASTBNK @@ *RP MOV,  \ Rpush the active bank
     *IP+ R1 MOV,         \ fetch bank# from DATA FIELD -> R1, inc IP
     MAPPER @@ BL,        \ & switch to SAMS page for this word
     *IP IP MOV,          \ get SAMS DP & set new IP
     NEXT,

: FAR: ( -- ) \ special colon for words in FAR memory
     !CSP
     HEADER                \ compile Forth header with name
     FARCOL ,              \ compile the new executor as CFA
     BANK#@  ,            \ compile bank# as the DATA field
     BANK#@ ]DP @ ,     \ compile this word's SAMS address ( ie: FAR XT)

     HERE SAVHERE !        \ save "normal here"
     BANK#@ ]DP @ DP !  \ set dp to CSEG. Compiling goes here now
     BANK#@ CMAP          \ map SAMS for compiling
     HIDE
     ]                    \ turn on the compiler
;

HEX
CODE FAREXIT
     R1 RPOP,
     MAPPER @@ BL,
     IP RPOP,
     NEXT,
ENDCODE

: FARSEMIS ( -- )
      POSTPONE FAREXIT
      POSTPONE [
      REVEAL ?CSP ; IMMEDIATE

: ;FAR ( -- ) \ end banked compilation
      POSTPONE GOTO  SAVHERE @ ,
      HERE  BANK#@ ]DP !      \ update here for bank
      SAVHERE @ DP !             \ restore dp to "normal" memory
      POSTPONE FARSEMIS
; IMMEDIATE

DECIMAL
: SETBANK ( bank# -- ) \  0..15 are valid args
      DUP 256 240 WITHIN ABORT" Bad bank number"
      BANK# !
;

HEX
: _BFREE ( -- n) 4000  BANK#@ ]DP @ - ;
: .BFREE ( -- ) DECIMAL
    CR ." Bank# " BANK#@  . ." , "  _BFREE  .  ." bytes free." CR ;

HERE SWAP -
DECIMAL CR . .( bytes)  \ free 11,566

PASSTHRU CMAP  \ init the Forth memory bank# as LASTBNK
\ REMOVE-TOOLS
HEX
F0 SETBANK

 

 

Here is a test suite using a SAMS wordlist to hold a the SAMS version of  :  and  ;   plus all the SAMS words. 

 

Spoiler

\ redirecting : and ;
INCLUDE DSK1.WORDLISTS
ONLY FORTH DEFINITIONS
\ rename normal : ;  so we don't over-ride them and can still use them
: H:   :   ;
: ;H   POSTPONE ;  ;  IMMEDIATE

VOCABULARY SAMS
ALSO SAMS DEFINITIONS
\ Put : and ; in SAMS VOCABULARY as aliased FAR:  ;FAR
H: :    FAR:  ;H
H: ;   POSTPONE ;FAR  ;H  IMMEDIATE

\ SAMS must first in the search order to find the special
\ version of : and ;

FORTH DEFINITIONS
INCLUDE DSK1.DEFER  \ Defer seems to break SAMS compiling ??

SAMS DEFINITIONS
HEX F0 SETBANK
INCLUDE DSK1.TOOLS

SAMS DEFINITIONS
HEX F1 SETBANK
INCLUDE DSK1.ANSFILES
INCLUDE DSK1.CATALOG
INCLUDE DSK1.MORE

SAMS DEFINITIONS
HEX F2 SETBANK
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.DIRSPRIT
INCLUDE DSK1.COOLSPRITE

ONLY SAMS ALSO FORTH DEFINITIONS

 

 

  • Like 1
Link to comment
Share on other sites

So I did a benchmark program in two versions.

 

One that lives in completely in CPU RAM.

 

The 2nd has the outer loop in SAMS F9 but the inner loop is in SAMS F8. 

This forces a cross SAMS page call. :) 

 

Here is the code.

Spoiler

\ sams benchmark vs CPU RAM
INCLUDE DSK1.WORDLISTS

ONLY FORTH DEFINITIONS
INCLUDE DSK1.VALUES
INCLUDE DSK1.ELAPSE

\ rename normal : ;  so we don't over-ride them and can still use them
: H:   :   ;
: ;H   POSTPONE ;  ;  IMMEDIATE

\ redirecting : and ;
VOCABULARY SAMS
ALSO SAMS DEFINITIONS
H: :    FAR:  ;H
H: ;   POSTPONE ;FAR  ;H  IMMEDIATE

FORTH DEFINITIONS
HEX
  5 CONSTANT FIVE
100 CONSTANT MASK
  0 VALUE BVAR

: INNERBENCH
    BEGIN
      DUP SWAP DUP ROT DROP 1 AND
      IF FIVE +
      ELSE 1-
      THEN TO BVAR
      BVAR DUP MASK AND
    UNTIL ;

: BENCHIE  MASK 0 DO  1 INNERBENCH  DROP  LOOP ;

\ __________________________________________
SAMS DEFINITIONS
F8 SETBANK      \ Force a call to another SAMS bank
: INNERBENCH
    BEGIN
      DUP SWAP DUP ROT DROP 1 AND
      IF FIVE +
      ELSE 1-
      THEN TO BVAR
      BVAR DUP MASK AND
    UNTIL ;

F9 SETBANK
: BENCHIE  MASK 0 DO  1 INNERBENCH  DROP  LOOP ;

FORTH ELAPSE BENCHIE
SAMS  ELAPSE BENCHIE

 

 

The screen shots tell the tale. Pages were toggling...

image.thumb.png.af606d1d9388eec8dc39c54d8d8a26c9.png 

 

Speed is VERY good.

Classic99 QI399.046 2021-10-07 9_07_51 PM.png

  • Like 3
Link to comment
Share on other sites

I was doing some folder housekeeping and found something I tried to do a long time ago.

I guess I have learned something over the last few years because it was clear to me that it was all wrong. :) 

 

The objective was to make a character pattern compiler the lets you "draw" your character patterns in text in your source code like these examples.

CREATE BIG-O
\  01234567
 |  ****   |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 |  ****   |

CREATE SQUARE
\  01234567
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|

 

The better idea was to convert a string of asterisks and spaces into a string of ones and zeros.

A string of ones and zeros can be "evaluated" by Forth as a binary number.

That number can then be compiled into memory as one byte of a pattern definition.

 

It's not nearly as fancy as a program that let's you draw on the screen but its a heck of lot easier than HEX bytes in a string.  :)

 

Here is the code with the examples above.

Code Edit: Removed HOLDER buffer and used PAD instead. Saves bytes  

 

Spoiler

\ Character shape compiler using text    Oct 12, 2021     B Fox

DECIMAL

VARIABLE PH

: BINARY   2 BASE ! ;

: DIGIT!  ( byte -- )
        PAD PH @ + C! \ store byte in PAD buffer
        PH 1+!        \ increment the PAD pointer
        1 PAD C+!  ;  \ increment the count byte

: STAR># ( char -- )     \ convert '*' to one or space to zero
        [CHAR] * =
        IF   [CHAR] 1 DIGIT!    \ store characters in PAD
        ELSE [CHAR] 0 DIGIT!
        THEN  ;

: >STAR#S  ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros
        PAD OFF   1 PH !        \ init PAD and point position
        BOUNDS DO  I C@ STAR>#  LOOP
        PAD COUNT  ;            \ return PAD as a stack string

: |   ( -- )
       [CHAR] | PARSE-WORD  >STAR#S  \ parse string, convert to binary # string
       BINARY EVALUATE  C, ;         \ evaluate the string & compile the byte

HEX   ( these are in DSK1.GRAFIX )
: ]PDT     ( char# -- 'pdt[n] )  8* 800 + ;
: CHARDEF  ( addr char# --)  ]PDT 8 VWRITE ;


CREATE BIG-O
\  01234567
 |  ****   |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 |  ****   |

CREATE SQUARE
\  01234567
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|

DECIMAL
BIG-O  0 CHARDEF
SQUARE  1 CHARDEF
CR 0 EMIT  SPACE  1 EMIT

\ You can remove the shape compiler from the system when you are done with it.
\ but the character patterns DATA stays in VDP RAM.
\ FORGET PH

 

 

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

9 hours ago, TheBF said:

I was doing some folder housekeeping and found something I tried to do a long time ago.

I guess I have learned something over the last few years because it was clear to me that it was all wrong. :) 

 

The objective was to make a character pattern compiler the lets you "draw" your character patterns in text in your source code like these examples.


CREATE BIG-O
\  01234567
 |  ****   |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 |  ****   |

CREATE SQUARE
\  01234567
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|

 

The better idea was to convert a string of asterisks and spaces into a string of ones and zeros.

A string of ones and zeros can be "evaluated" by Forth as a binary number.

That number can then be compiled into memory as one byte of a pattern definition.

 

It's not nearly as fancy as a program that let's you draw on the screen but its a heck of lot easier than HEX bytes in a string.  :)

 

Here is the code with the examples above.

  Reveal hidden contents


\ Character shape compiler using text    Oct 12, 2021     B Fox

VARIABLE PH
DECIMAL
CREATE HOLDER  10 ALLOT      HOLDER 10 0 FILL

: BINARY   2 BASE ! ;

: DIGIT!  ( byte -- )
        HOLDER PH @ + C! \ store byte in holder string
        PH 1+!           \ increment the holder pointer
        1 HOLDER C+!  ;  \ increment the count byte

: STAR># ( char -- )     \ convert '*' to one or space to zero
        [CHAR] * =
        IF   [CHAR] 1 DIGIT!    \ store characters in HOLDER
        ELSE [CHAR] 0 DIGIT!
        THEN  ;

: >STAR#S  ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros
        HOLDER OFF   1 PH !        \ init HOLDER and point position
        BOUNDS DO  I C@ STAR>#  LOOP
        HOLDER COUNT  ;            \ return HOLDER as a stack string

: |   ( -- )
       [CHAR] | PARSE-WORD  >STAR#S  \ parse string, convert to binary # string
       BINARY EVALUATE  C, ;         \ evaluate the string & compile the byte

HEX   ( these are in DSK1.GRAFIX )
: ]PDT     ( char# -- 'pdt[n] )  8* 800 + ;
: CHARDEF  ( addr char# --)  ]PDT 8 VWRITE ;


CREATE BIG-O
\  01234567
 |  ****   |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 |  ****   |

CREATE SQUARE
\  01234567
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|

DECIMAL
BIG-O  0 CHARDEF
SQUARE  1 CHARDEF
CR 0 EMIT  SPACE  1 EMIT

\ You can even remove the shape compiler from the system when you are done with it.
\ but the character patterns DATA stays in VDP RAM.
FORGET PH

 

 

Neat. Cool

Link to comment
Share on other sites

I updated the code above to remove the HOLDER buffer and just used PAD.

 

Then I realized if you wanted to, the compiler could just put the bytes directly into VDP RAM in the pattern table.

You just replace C, with VC,   

 

Then you just need a word to set the VDP memory pointer to the correct location in the pattern table for the character you want to change.

This code does that.  So no CPU RAM is used to hold the patterns.

The downside of course is that the patterns have to be re-compiled each time you want them.

But I see this as simple way to design patterns on real iron.

Spoiler

\ Character shape compiler Direct to VDP   Oct 12, 2021     B Fox

: TASK ; 

INCLUDE DSK1.VDPMEM

DECIMAL

VARIABLE PH

: BINARY   2 BASE ! ;

: DIGIT!  ( byte -- )
        PAD PH @ + C! \ store byte in PAD buffer
        PH 1+!        \ increment the PAD pointer
        1 PAD C+!  ;  \ increment the count byte

: STAR># ( char -- )     \ convert '*' to one or space to zero
        [CHAR] * =
        IF   [CHAR] 1 DIGIT!    \ store characters in PAD
        ELSE [CHAR] 0 DIGIT!
        THEN  ;

: >STAR#S  ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros
        PAD OFF   1 PH !        \ init PAD and point position
        BOUNDS DO  I C@ STAR>#  LOOP
        PAD COUNT  ;            \ return PAD as a stack string

: |   ( -- )
       [CHAR] | PARSE-WORD  >STAR#S  \ parse string, convert to binary # string
       BINARY EVALUATE  VC, ;         \ evaluate the string & compile the byte

HEX 800 CONSTANT PDT
: SHAPE ( ascii -- ) 8* PDT +  VP ! ;  \ set VDP memory to the char's pattern table

DECIMAL
0 SHAPE
\  01234567
 |  ****   |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 | *    *  |
 |  ****   |

1 SHAPE
\  01234567
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|
 | ********|

CHAR A SHAPE
 \ 01234567
 |         |
 |    *    |
 |   * *   |
 |  *   *  |
 |  *****  |
 |  *   *  |
 |  *   *  |
 |         |

DECIMAL

CR  0 EMIT
CR  1 EMIT
CR 65 EMIT
\ Remove shape compiler from the system when you are done with it.
\ FORGET TASK

 

 

  • Like 2
Link to comment
Share on other sites

Obsessed with SAMS

 

I have managed to take out 4 more bytes per definition. Two in SAMS memory and two in the forth Dictionary entry. 

I removed the GOTO SAVEHERE line and replaced it with FARSEMIS.

This is a run-time exit from a SAMS definition so it can go in SAMS memory at the end of a definition.

The caller's IP address is pulled from the return stack so that's that.

 

Changing the dictionary pointer is a compile time complication so that is handled by ;FAR when we compile a new SAMS word.

 

My ultimate goal here is to get the entire FAR: definition living in SAMS memory and then modify FIND to search through SAMS banks first in declining order before searching Forth.  This will make FAR: definitions look similar to ANS Forth wordlists but we can have a lot more of these SAMS lists than wordlists. I only have room for 9 in the kernel.

 

Spoiler

\ Code in SAMS memory based on concept in TurboForth by Mark Wills
\ Ported to Camel99 Forth  Oct 13, 2021

\ Changes from original:
\ Remove bank stack.  Used return stack for bank# storage
\ Removed BANKS word. Changed to preset DP array
\ CMAP is a fast sub-routine for mapping SAMS pages F0..FF
\      CMAP Remembers the last bank that was paged in variable LASTBNK
\ Changed to compile a far "colon" definition to reduce dictionary overhead

\ Oct 7, 2021:  Compares input to CMAP to LAstbnk and
\               does nothing if page is still in memory
\ Oct 13, 2021  Remove GOTO in FAR exit. Not needed.
\               Compile FARSEMIS on the SAMS side, not in RAM Dictionary
\               Saves 2 bytes in dictionary and in 2 bytes SAMS definition

\ FAR word data structure:
\   CELL: link
\   BYTE: immediate field
\   BYTE: name length
\   BYTES: <....>
\   CELL:  code field
\   CELL:  DATA field #1 , bank#
\   CELL:  DATA field #2 , SAMS code field address
\ 10 bytes + name length

INCLUDE DSK1.MARKER
INCLUDE DSK1.ASM9900

HERE
F0 CONSTANT _1STBANK
FF CONSTANT _MAXBANK

\ SAMS memory management for code
HEX              3000 CONSTANT CSEG      \ code seg in CPU RAM
4000 CSEG 0B RSHIFT + CONSTANT CREG      \ compute CSEG SAMS register
CSEG 0C RSHIFT        CONSTANT PASSTHRU  \ default page for CSEG

DECIMAL
CREATE []DP  \ DP for 0 .. 15 pages of SAMS
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,
  CSEG , CSEG , CSEG , CSEG  ,

HEX
CODE ]DP ( bank# -- addr )
     TOS _1STBANK NEGATE  AI,  \ remove first bank offset to index []DP
     TOS TOS ADD,
     TOS []DP AI,
     NEXT,
ENDCODE

 VARIABLE  SAVHERE   \ temp holder for RAM Dictionary pointer
 VARIABLE  BANK#     \ active SAMS bank# for compiling
 VARIABLE  LASTBNK   \ last bank# passed to MAPPER

CODE BANK#@   ( -- bank#)  \ Used 8 times so this saves space and is fast
      TOS PUSH,
      BANK# @@ TOS MOV,
      NEXT,
ENDCODE

HEX
CREATE MAPPER ( R1: 0 .. 16 )  \ smart MAPPER
      R1 LASTBNK @@ CMP,  \ already mapped?
      NE IF,
         R1 LASTBNK @@ MOV,  \ update the last bank used
         R1 SWPB,          \ swap bytes
         R12 1E00 LI,      \ DO CMAP
         0 SBO,            \ turn on the card
         R1 CREG @@ MOV,   \ restore bank# from return stack
         0 SBZ,            \ turn off card
      ENDIF,
      RT,

CODE CMAP  ( bank# --) \ Forth word to map SAMS pages
      TOS R1 MOV,
      MAPPER @@ BL,
      TOS POP,
      NEXT,
      ENDCODE


CREATE FARCOL   \ run time executor for SAMS colon words.
     IP RPUSH,
     W IP MOV,
     RP DECT,
     LASTBNK @@ *RP MOV,  \ Rpush the active bank
     *IP+ R1 MOV,         \ fetch bank# from DATA FIELD -> R1, inc IP
     MAPPER @@ BL,        \ & switch to SAMS page for this word
     *IP IP MOV,          \ get SAMS DP & set new IP
     NEXT,

: FAR: ( -- ) \ special colon for words in FAR memory
     !CSP
     HEADER               \ compile Forth header with name
     FARCOL ,             \ compile the new executor as CFA
     BANK#@ ,             \ compile bank# as the DATA field
     BANK#@ ]DP @ ,       \ compile this word's SAMS address ( ie: FAR XT)

     HERE SAVHERE !       \ save "normal here"
     BANK#@ ]DP @ DP !    \ set dp to CSEG. Compiling goes here now
     BANK#@ CMAP          \ map SAMS for compiling
     HIDE
     ]                    \ turn on the compiler
;

HEX
CODE FAREXIT
     R1 RPOP,            \ get bank# save by FARCOL
     MAPPER @@ BL,       \ map it in
     IP RPOP,            \ Regular FORTH EXIT
     NEXT,
ENDCODE

: FARSEMIS ( -- )
      POSTPONE FAREXIT
      POSTPONE [        \ turn compiler off
      REVEAL ?CSP ; IMMEDIATE

: ;FAR ( -- ) \ end banked compilation
      POSTPONE FARSEMIS           \ compile ending in SAMS part of definition
      HERE  BANK#@ ]DP !          \ update here for this bank
      SAVHERE @ DP !              \ restore dp to CPU RAM
; IMMEDIATE

DECIMAL
: SETBANK ( bank# -- ) \  0..15 are valid args
      DUP 256 240 WITHIN ABORT" Bad bank number"
      BANK# !
;

HEX
: _BFREE    ( -- n) 4000  BANK#@ ]DP @ - ;
: .BFREE ( -- ) DECIMAL
    CR ." Bank# " BANK#@  . ." , "  _BFREE  .  ." bytes free." CR ;

HERE SWAP -
DECIMAL CR . .( bytes)  \ free 11,566

PASSTHRU CMAP  \ init the Forth memory bank# as LASTBNK
\ REMOVE-TOOLS
HEX
F0 SETBANK

 

 

I have also moved my INCLUDE line buffer into high RAM. I was using PAD but when INCLUDEing into a SAMS block PAD is in the SAMS memory and so it prevents using the last 127 bytes of the SAMS bank.

 

The other thing that is interesting is how compact headless Forth code is. 

All these files compiled into 4K with 380 bytes to spare. Of course there was another 1800 bytes of header code over in CPU RAM.

(I need to make a way to leverage this idea to create headless programs that can be saved as EA5 images.) 

DECIMAL 240 SETBANK
HEX
INCLUDE DSK1.ELAPSE
INCLUDE DSK1.UDOTR
INCLUDE DSK1.ANSFILES
INCLUDE DSK1.CATALOG
INCLUDE DSK1.DIR
INCLUDE DSK1.MORE
INCLUDE DSK1.BUFFER
INCLUDE DSK1.COMPARE
INCLUDE DSK1.CASE
INCLUDE DSK1.VALUES
INCLUDE DSK1.TRAILING
INCLUDE DSK1.SYNONYM
INCLUDE DSK1.3RD4TH
INCLUDE DSK1.MALLOC
INCLUDE DSK1.VDPMEM
INCLUDE DSK1.FASTCASE
INCLUDE DSK1.ASMLABELS
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.DIRSPRIT
INCLUDE DSK1.COOLSPRITE

 

 

  • Like 4
Link to comment
Share on other sites

Sometimes maintenance is worthwhile.

 

I had hacked together a version of -TRAILING like this. (-TRAILING removes trailing spaces from a string)

CODE -TRAILING ( addr len -- addr len') 
     *SP  W  MOV,   \ DUP addr in W
      TOS W  ADD,   \ add len
      R1 2000 LI,   \ R1 holds the space character
      W DEC,        \ w=address of last char
      BEGIN,
        *W R1 CMPB, \ compare to ascii >20
      EQ WHILE,
         W   DEC,   \ move back 1 char
         TOS DEC,   \ dec len in TOS
      REPEAT,
      TOS 0 CI,
      1 JGT,
      TOS CLR,
  NEXT,
  ENDCODE
 

Looking at it I didn't like having two DEC instructions inside the loop and I really didn't like the negative length test at the end.

 

This is about 13% faster. It seems to test solidly.

HEX
CODE -TRAILING ( addr len -- addr len')
     *SP TOS  ADD,     \ add len
         TOS  DEC,
      R1 2000 LI,      \ R1 holds the space character
      BEGIN,
        *TOS R1 CMPB,  \ compare to ascii >20
      EQ WHILE,
            TOS DEC,   \ move back 1 char
      REPEAT,
    *SP TOS SUB,
        TOS INC,
   NEXT,
ENDCODE

 

  • Like 4
Link to comment
Share on other sites

"It takes nine months to have a baby no matter how many women you put on the job."  (Fred Brookes, The Mythical Man Month)

 

Well actually my newest granddaughter arrived in 8 months plus one week, about 2 weeks ago and she is really cute.

 

Unlike little baby Ivory, my documentation for the linker took longer than it should but here it is.

 

I included @FarmerPotato 's disk with some additions of my own to aid in the getting used to this beasty.

I have been testing on an unreleased version of Camel99 (2.68F)  but it should work on the latest released version.

 

Maybe it is of some use to someone out there.  :) 

Erik I believe your original question was because you have some bit map graphics code that you want to run under Forth.

If you want to post it here I can take a run at making it work (if it assembles with the standard TI Assembler) 

 

LINKER99.zip

  • Like 4
Link to comment
Share on other sites

I have a feeling that I posted this already but it might have been on comp.lang.forth.

Can't find the title here so here goes...

 

While looking for more crazy people on the internet who like stack architectures I re-found this paper by another Canuck,  Charles LaForet who did his undergrad at Waterloo U.

This paper has some neat history in the Historical Review chapter (where else) on the origins of Polish notation, reverse Polish notation and even some very early stack machines.

There is also a ton of research on pros and cons of registers and stacks.

 

I love the dedication page! :) 

 

http://fpgacpu.ca/stack/Second-Generation_Stack_Computer_Architecture.pdf

  • Like 1
Link to comment
Share on other sites

@Retrospect created some really nice demos for XB256 in the Billy Ball topic and I just couldn't resist trying to do this with Camel99 Forth.

I have zero imagination about games so I need to leverage his kind of imagination if I ever want to push the system. 

 

@Retrospect did an excellent job IMHO of organizing the code around a large set of sub-routines that live in a loop to control all the activities.

That is the normal way to do this kind of thing.

 

I wondered if I could do it with a set of tasks that manage the motion of Billy and Bobby independently and also manage the shooting in a task.

I don't have it done yet but the concept seems to work.

It was always risky for me to try since I have to stand on the pyramid of code that I built over the past few years. :) 

 

Something that is interesting about this idea, something I don't think about much, but it's called re-entrant code.

This is code that runs with it's own copy of data so that many tasks can use the same routine at the same time. 

I tried this with ROTATOR and BOUNCER, using the same code to run Billy Ball and  Bobby Ball. It's kind of neat. :)

Forth is kind a natural for this if you use the data stack for everything or use  special "user variables" that can be created for each task. 

 

Another handy thing is Forth's MS word which looks like a delay in milli-seconds but while it's counting down it passes control to the next task in the task queue so everything keeps running smoothly.  This makes delays much easier to manage.

 

Here is the code and the video shows the current state of what it can do. (not very much)

I also compiled MTOOLS in the video so I could show some of the stats of the tasks.

The system is looking pretty stable when I can compile code while sprites are moving. :) 

 

The Pulse number at the end of the video is the time it takes for the entire task queue to be visited and return to the console task. 

4 mS is not too bad for our favourite old computer on four active tasks. Gotta love those workspace context switches!. 

 

Spoiler

\ BILLYBALL XB256 DEMO by @Retrospect on atariage.com   Nov 1 2021
\ Test harness for Camel99 forth  B Fox

INCLUDE DSK1.TOOLS  \ DEBUG ONLY
INCLUDE DSK1.MARKER
INCLUDE DSK1.MALLOC
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.RANDOM
INCLUDE DSK1.SOUND
INCLUDE DSK1.DIRSPRIT \ direct control sprites
INCLUDE DSK1.MTASK99

\ ***********************
\ CHAR DEFINITION HELPERS
\ ***********************
DECIMAL
: CHARDEF32 ( data[] ascii# -- ) ]PDT 32 VWRITE ; \ def 4 chars at once

\ convert long text string to 16 bit HEX numbers and
\ compile each number into memory sequentially
: HEX#, ( addr len  --)
        BASE @ >R        \ save radix
        HEX              \ converting string to hex numbers
        BEGIN DUP
        WHILE            \ while len<>0
            OVER 4       \ used 4 digits from left end of string
            NUMBER? ABORT" Bad number"  \ convert string to number
             ,           \ compile the integer into memory
            4 /STRING    \ cut 4 digits off left side of string
        REPEAT           \ keep going until string is exhausted
        2DROP
        R> BASE !        \ restore radix
;

\ ***********************
\ * BALL ANIMATION DEFS *
\ ***********************
\ Compile contiguos data for each frame of Ball animation
CREATE BALLS ( patterns for 23 chars )
S" 00030F1F3F3C787A787F7F3C3E1F0F0300E0F8FCFE9E8FAF8FFFFF1E3EFCF8E0" HEX#,
S" 00030F1F3F397175717F7F383C1F0F0300E0F8FCFE3E1F5F1FFFFF3E7EFCF8E0" HEX#,
S" 00030F1F3F32626A627F7F30381F0F0300E0F8FCFE7E3FBF3FFFFF7EFEFCF8E0" HEX#,
S" 00030F1F3F244455447F7F20311F0F0300E0F8FCFEFE7F7F7FFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F09082A087F7F01231F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F131155117F7F03071F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F27232B237F7F070F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F0F4757477F7F0F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F1F0F2F0F7F7F1F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F1F1F5F1F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCFCFDFCFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCF8FAF8FFFFFCFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF8F1F5F1FFFFF8FEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF2E2EAE2FFFFF0F8FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEE4C4D5C4FFFFE0F0FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEC888AA88FFFFC0E2FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFE92115511FFFF82C6FCF8E0" HEX#,
S" 00030F1F3F3F7E7E7E7F7F3F3F1F0F0300E0F8FCFE2623AB23FFFF068EFCF8E0" HEX#,
S" 00030F1F3F3E7C7D7C7F7F3E3F1F0F0300E0F8FCFE4E475747FFFF0E1EFCF8E0" HEX#,

\ expose BALLS as an array of 32 byte records
: ]BALL ( n -- addr )  32 * BALLS +  ;

CREATE EXPLOSION
S" 0030787C3E1C0070FCF8F83103030100000E1E1C382000071F0F8680C0E08000" HEX#,

CREATE MISSLE
S" 000000000000000000000F1F0F00000000000000000000000000E0F0E0000000" HEX#,

\ **************
\ * STAR CHR'S *
\ **************
DECIMAL
CREATE STARS  160 , 168 , 176 , 184 , 192 , 200 , 208 ,
: ]STAR ( n -- addr)    CELLS STARS + ;

PAD CHAR . CHARPAT     \ read '.' char pattern
PAD  0 ]STAR CHARDEF   \ assign to star characters
PAD  1 ]STAR CHARDEF
PAD  2 ]STAR CHARDEF
PAD  3 ]STAR CHARDEF
PAD  4 ]STAR CHARDEF
PAD  5 ]STAR CHARDEF
PAD  6 ]STAR CHARDEF

\ *****************************
\ MAKE SPRITES
\ *****************************
DECIMAL
2 MAGNIFY
128 CONSTANT Billy
132 CONSTANT Bobby
136 CONSTANT Missle

 02 CONSTANT WEAPON


( char colr   x   y sp# -- )
  Billy  16   10  10  0  SPRITE
  Bobby   5  215  10  1  SPRITE
  Missle  2   20  20 WEAPON SPRITE

  MISSLE 136 CHARDEF32

\ *********************
\ Multi-Task actions must be in an endless loop. Control with WAKE/SLEEP
\ *********************
: ROTATOR ( char -- )
     BEGIN
       23 0 DO
         I ]BALL OVER  CHARDEF32
         60 MS
       LOOP
     AGAIN ;

: BOUNCER ( spr# --)
     BEGIN
       100 10 DO   I OVER SP.Y VC! 40 MS     LOOP
       10 100 DO   I OVER SP.Y VC! 40 MS  -1 +LOOP
     AGAIN ;

: SPARKLER ( stars[] -- ) ;

\ INC/DEC byte in VDP RAM
: +!V   ( n Vaddr -- ) S" TUCK VC@ +  SWAP VC!" EVALUATE ; IMMEDIATE

: STOP  ( pid -- ) SLEEP PAUSE ;

DECIMAL
: SHOOT   ( -- )
        7 NOISE 0 DB
        16 0 DO  I DB 30 MS PAUSE LOOP MUTE ;

: EXPLODE ( -- )
      4 NOISE 0 DB  100 MS
      5 NOISE
      16 0 DO  PAUSE I DB 60 MS   LOOP MUTE ;

: FIRE  ( sp.X sp.Y -- )
      SHOOT
      0 POSITION WEAPON LOCATE  \ Put weapon inside sprite 0
      9 WEAPON SP.COLOR         \ give it a color
      8 DB
      240 0
      DO
        1  WEAPON SP.X +!V
        1 WEAPON 10 COINC
        IF
          1  WEAPON SP.COLOR
          EXPLODE
          LEAVE
        THEN PAUSE
     LOOP
     MUTE
     1  WEAPON SP.COLOR
;

: SPIN-BILL   Billy ROTATOR ;
: SPIN-BOB    Bobby ROTATOR ;

: BOUNCE-BILL  0 BOUNCER ;
: BOUNCE-BOB   1 BOUNCER ;

\ task creator
: TASK: ( n -- ) USIZE MALLOC DUP FORK CONSTANT  ; \ returns PID (address)

DECIMAL
TASK: JOB1   TASK: JOB2    \ BILLY
TASK: JOB3   TASK: JOB4    \ BOBBY
TASK: JOB5                 \ stars

' SPIN-BILL   JOB1 ASSIGN
' BOUNCE-BILL JOB2 ASSIGN

' SPIN-BOB    JOB3 ASSIGN
' BOUNCE-BOB  JOB4 ASSIGN
MULTI

CLEAR

 

 

 

 

 

  • Like 2
Link to comment
Share on other sites

That's great!  Seeing it "live" like that whilst you've got the parser running is weird!  Glad you're having fun with it. :)
I won't be able to get my head around this Forth stuff though but it looks mega cool.  

If you wanna do an explosion for bobby that continue whilst other stuff is going on , in my XB256 example I had a variable called EX2 and EXT2 ... EX2 is turned off whilst Bobby is alive.  When he's hit, EX2 is on.  Most of the routines that involve the movement and firing of Bobby have a check to see if EX2 is switched on.  If it is, his movement routine is ignored, as are the Bobby firing routines and more importantly it ignores the coincidences for Billy's missiles against bobby.  The explosion time counts up using EXT2, till a certain value is reached and then it's over.  

 

EX1 and EXT1 are for Billy but they're unused in my example as it was just a scribble of sorts but I decided to document my scribble with good comments so that other people can scribble too.  ;)

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

29 minutes ago, Retrospect said:

That's great!  Seeing it "live" like that whilst you've got the parser running is weird!  Glad you're having fun with it. :)
I won't be able to get my head around this Forth stuff though but it looks mega cool.  

If you wanna do an explosion for bobby that continue whilst other stuff is going on , in my XB256 example I had a variable called EX2 and EXT2 ... EX2 is turned off whilst Bobby is alive.  When he's hit, EX2 is on.  Most of the routines that involve the movement and firing of Bobby have a check to see if EX2 is switched on.  If it is, his movement routine is ignored, as are the Bobby firing routines and more importantly it ignores the coincidences for Billy's missiles against bobby.  The explosion time counts up using EXT2, till a certain value is reached and then it's over.  

 

EX1 and EXT1 are for Billy but they're unused in my example as it was just a scribble of sorts but I decided to document my scribble with good comments so that other people can scribble too.  ;)

Ya the Forth stuff is definitely strange. And then it gets stranger with the separate tasks. It has taken me a while time to get somewhat comfortable with it. 

 

They way you describe your explosion method is how you do it when everything is in a common loop. (If I understand your code and what you wrote correctly)

In theory.... I can make an explosion task.  I just tell it to start and it will play it's explosion and even do some kind of fragmentation of poor Bobby and then it puts itself to sleep when it's finished. 

That will all happen while everything else is still running. :) 

How cool is that? So that's what I will try and see how it works out.

 

It is a really different brain process when you can make these "baby computers" (tasks)  and give them loops that have nothing to do with the main program. 

It is not free however. Each task in this system takes 192 bytes for the registers and system variables and each switch to task takes a some time as well. 

 

As I said before I am really grateful for your well commented examples and creative ideas.

 

  • Like 1
Link to comment
Share on other sites

Make that Camel Sweat

 

So @Retrospect just keeps cranking out the demonstrations.

The one that put all the "balls in the air" was interesting for me to see what happens if I created 10 tasks to move the balls and 10 more to spin the balls.

 

Overall the results are not bad IMHO.

The overhead per task when 20 are running looks like ~1.2 mS per task at least that is what my measurements say.

 

The code is not beautiful. I just used lots of text to define everything. I would have to add a bit more code to dynamically spawn and destroy tasks but that was not important for this test.

 

As you can see with a cooperative tasker when I added spin it loaded the system down even though I am not spinning very quickly.

Some of that is because we are calling VMBW which takes the entire machine until it is finished changing 4 character patterns.

I could fix that as well if this was a real application by chunking the pattern into small pieces but that might make the animation chunky too.

No such thing as a free lunch. :)

 

Another thing is that waiting for a key using KSCAN is about a 1.1mS affair so that slows all the task switching down as well.

The WAIT in the demo calls  >0020  which scans only for BREAK and is much faster. You can what happens when I use it. 

 

Anyway this was good workout for the Camel Multi-tasker. And so far everything worked as expected.

  

Spoiler

\ OHBALLS XB256 DEMO by @Retrospect on atariage.com   Nov 4 2021
\ Test harness for Camel99 forth  B Fox

INCLUDE DSK1.TOOLS  \ DEBUG ONLY
INCLUDE DSK1.MARKER
INCLUDE DSK1.MALLOC
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.RANDOM
INCLUDE DSK1.SOUND
INCLUDE DSK1.DIRSPRIT \ direct control sprites
INCLUDE DSK1.MTASK99
INCLUDE DSK1.MTOOLS

\ ***********************
\ CHAR DEFINITION HELPERS
\ ***********************
DECIMAL
\ pause makes this multi-tasker friendly
: CHARDEF32 ( data[] ascii# -- ) PAUSE ]PDT 32 VWRITE ; \ def 4 chars at once

\ convert long text string to 16 bit HEX numbers and
\ compile each number into memory sequentially
: HEX#, ( addr len  --)
        BASE @ >R        \ save radix
        HEX              \ converting string to hex numbers
        BEGIN DUP
        WHILE            \ while len<>0
            OVER 4       \ used 4 digits from left end of string
            NUMBER? ABORT" Bad number"  \ convert string to number
             ,           \ compile the integer into memory
            4 /STRING    \ cut 4 digits off left side of string
        REPEAT           \ keep going until string is exhausted
        2DROP
        R> BASE !        \ restore radix
;

\ ***********************
\ * BALL ANIMATION DEFS *
\ ***********************
\ Compile contiguos data for each frame of Ball animation
CREATE BALLS ( patterns for 23 chars )
S" 00030F1F3F3C787A787F7F3C3E1F0F0300E0F8FCFE9E8FAF8FFFFF1E3EFCF8E0" HEX#,
S" 00030F1F3F397175717F7F383C1F0F0300E0F8FCFE3E1F5F1FFFFF3E7EFCF8E0" HEX#,
S" 00030F1F3F32626A627F7F30381F0F0300E0F8FCFE7E3FBF3FFFFF7EFEFCF8E0" HEX#,
S" 00030F1F3F244455447F7F20311F0F0300E0F8FCFEFE7F7F7FFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F09082A087F7F01231F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F131155117F7F03071F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F27232B237F7F070F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F0F4757477F7F0F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F1F0F2F0F7F7F1F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F1F1F5F1F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCFCFDFCFFFFFEFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCF8FAF8FFFFFCFEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF8F1F5F1FFFFF8FEFCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF2E2EAE2FFFFF0F8FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEE4C4D5C4FFFFE0F0FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEC888AA88FFFFC0E2FCF8E0" HEX#,
S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFE92115511FFFF82C6FCF8E0" HEX#,
S" 00030F1F3F3F7E7E7E7F7F3F3F1F0F0300E0F8FCFE2623AB23FFFF068EFCF8E0" HEX#,
S" 00030F1F3F3E7C7D7C7F7F3E3F1F0F0300E0F8FCFE4E475747FFFF0E1EFCF8E0" HEX#,

\ expose BALLS as an array of 32 byte records
DECIMAL
: ]BALL ( n -- addr )  32 * BALLS +  ;

: >CHAR  ( n -- n')  8* 128 + ; \ convert sprite# to the character#

\ *********************
\ These 2 routines do all the work.
\ *********************
DECIMAL
: ROTATOR ( spr# -- )
     >CHAR
     BEGIN
       23 0
       DO
         I ]BALL OVER CHARDEF32
         120 MS
       LOOP
       PAUSE
     AGAIN ;

: TRAVELER ( spr# --) \ Endless loop. Must assign to a task.
     BEGIN
        PAUSE
        250 10 DO  PAUSE  I OVER SP.X VC!      LOOP
        10 250 DO  PAUSE  I OVER SP.X VC!  -1 +LOOP
        PAUSE
     AGAIN ;

\ Hard coded was the fastest way to assign the parameter (not fancy)
: ROTATE0        0  ROTATOR ;     : ROTATE1        1  ROTATOR ;
: ROTATE2        2  ROTATOR ;     : ROTATE3        3  ROTATOR ;
: ROTATE4        4  ROTATOR ;     : ROTATE5        5  ROTATOR ;
: ROTATE6        6  ROTATOR ;     : ROTATE7        7  ROTATOR ;
: ROTATE8        8  ROTATOR ;     : ROTATE9        9  ROTATOR ;

: TRAVEL0        0  TRAVELER ;    : TRAVEL1        1  TRAVELER ;
: TRAVEL2        2  TRAVELER ;    : TRAVEL3        3  TRAVELER ;
: TRAVEL4        4  TRAVELER ;    : TRAVEL5        5  TRAVELER ;
: TRAVEL6        6  TRAVELER ;    : TRAVEL7        7  TRAVELER ;
: TRAVEL8        8  TRAVELER ;    : TRAVEL9        9  TRAVELER ;

\ *****************************
\ MAKE SPRITES
\ *****************************
DECIMAL
2 MAGNIFY
: MAKE-BALLS
  10 0
  DO
    ( char       colr      x    y        sp# -- )
     I >CHAR    I 6 +     10   I 16 *   I SPRITE  \ make a sprite 

 \ set the char patterns. Each sprite gets a different phase of the rotation
     I ]BALL    I >CHAR  CHARDEF32 
   LOOP
   16 2 SP.COLOR  ;  ( fix the cyan sprite )

\ task creator
: TASK: ( n -- ) USIZE MALLOC DUP FORK CONSTANT  ; \ returns PID (address)

\ 20 tasks are created to rotate and move the balls independantly
TASK: MOVE0   ' TRAVEL0  MOVE0 ASSIGN
TASK: MOVE1   ' TRAVEL1  MOVE1 ASSIGN
TASK: MOVE2   ' TRAVEL2  MOVE2 ASSIGN
TASK: MOVE3   ' TRAVEL3  MOVE3 ASSIGN
TASK: MOVE4   ' TRAVEL4  MOVE4 ASSIGN
TASK: MOVE5   ' TRAVEL5  MOVE5 ASSIGN
TASK: MOVE6   ' TRAVEL6  MOVE6 ASSIGN
TASK: MOVE7   ' TRAVEL7  MOVE7 ASSIGN
TASK: MOVE8   ' TRAVEL8  MOVE8 ASSIGN
TASK: MOVE9   ' TRAVEL9  MOVE9 ASSIGN

TASK: SPIN0   ' ROTATE0  SPIN0 ASSIGN
TASK: SPIN1   ' ROTATE1  SPIN1 ASSIGN
TASK: SPIN2   ' ROTATE2  SPIN2 ASSIGN
TASK: SPIN3   ' ROTATE3  SPIN3 ASSIGN
TASK: SPIN4   ' ROTATE4  SPIN4 ASSIGN
TASK: SPIN5   ' ROTATE5  SPIN5 ASSIGN
TASK: SPIN6   ' ROTATE6  SPIN6 ASSIGN
TASK: SPIN7   ' ROTATE7  SPIN7 ASSIGN
TASK: SPIN8   ' ROTATE8  SPIN8 ASSIGN
TASK: SPIN9   ' ROTATE9  SPIN9 ASSIGN

: TRAVEL
  MOVE0 RESTART 50 MS   \ RESTART each one and wait
  MOVE1 RESTART 50 MS
  MOVE2 RESTART 50 MS
  MOVE3 RESTART 50 MS
  MOVE4 RESTART 50 MS
  MOVE5 RESTART 50 MS
  MOVE6 RESTART 50 MS
  MOVE7 RESTART 50 MS
  MOVE8 RESTART 50 MS
  MOVE9 RESTART 50 MS
;

: SPINALL
  SPIN0 RESTART 250 MS
  SPIN1 RESTART 250 MS
  SPIN2 RESTART 250 MS
  SPIN3 RESTART 250 MS
  SPIN4 RESTART 250 MS
  SPIN5 RESTART 250 MS
  SPIN6 RESTART 250 MS
  SPIN7 RESTART 250 MS
  SPIN8 RESTART 250 MS
  SPIN9 RESTART 250 MS
;

: WAIT   BEGIN   PAUSE PAUSE PAUSE ?TERMINAL   UNTIL ;

MULTI
MAKE-BALLS
CLEAR

 

 

  • Like 4
Link to comment
Share on other sites

I was curious if my ideas about writing 32 bytes to the pattern table at once was hogging too much CPU time.

 

I broke up the code that writes 32 bytes into four chunks, writing data for one character at a time.

After each chunk is written there is a PAUSE to allow other tasks to run.

DECIMAL
\ chopped into 4 pieces for smooth multi-tasking
: CHARDEF4 ( data[] ascii# -- )
        >R    ( hold ascii# on Rstack like a local variable)
            DUP R@     CHARDEF   PAUSE
        8 + DUP R@ 1+  CHARDEF   PAUSE
        8 + DUP R@ 2+  CHARDEF   PAUSE
        8 +     R> 3 + CHARDEF   PAUSE
;

Looks like the context switching time is still so small that  I can't see any difference in the face rotation.

I am impressed! :) 

 

This gives more time for the travel code or whatever you need to run. 

 

This also means that I might be better off making the BALLS array index 1 character pattern at a time rather than four.

 

This is the one big thing you have to manage with a cooperative tasker, is tuning the code to give everybody the correct amount of time. The rule of thumb is every I/O operation needs at least one PAUSE.  The reality is you have to test to make sure you got what you need. The good news is that PAUSE makes it pretty simple to adjust your code.

 

Another cool thing is it does not interfere at all with putting other jobs on the interrupt if you want to. They can interoperate seamlessly.  I could move the balls with AUTOMOTION and rotate the faces with Forth code. 

 

I added screen color changes to WAIT so we see something similar to the original XB256 demo.

: WAIT ( -- )
          2 SCREEN
          BEGIN   PAUSE ?TERMINAL   UNTIL
          8 SCREEN ;

I have a better feel for what's possible with this tasker now on TI-99.

I think I have exhausted this for now.

 

 

 

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

14 hours ago, GDMike said:

Its quick

Yes it is thanks to the clever 9900 architecture.

 

To jump from one task to the next task is one instruction. RTWP.

Then we test the task's local awake/asleep variable. If the task is asleep try the next one.

Sleeping tasks run 4 instructions. (I am  sure Windows is just as efficient...)

 

YIELD    RTWP           * Enter next task’s workspace
         STWP R1        * Use the WP as base address  
         MOV >20(R1),R0 * read the TFLAG variable 
         JEQ YIELD      * if task is asleep jump back

I knew something like this was possible 35 years ago but did not know how to "git 'er done".  

The big revelation was to "pre-setup"  R13,R14,R15 in each  task when it is created so you can  switch to it with RTWP not BLWP.

The workspace is just memory so why keep separate vectors when the very same data is in the workspace? 

This little piece of code made my life feel better. :) 

Does that make me a nerd? ?

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

The key routine should be just a CRU scan for keyboard key being pressed and a word scan for any bit changes.

That way the IO CHIP is doing the work instead of CPU.

This should speed up any test for a keypress.

After all CRU bits 6 to 14 are Keyboard bits and looking at these bits is faster then checking all CRU lines.

i.e. 6=binary 110 and 14=binary 1110

Thus if these 4 bits are a 1 you have a key pressed, if all are 0000 then no key.

Edited by RXB
Link to comment
Share on other sites

2 hours ago, RXB said:

The key routine should be just a CRU scan for keyboard key being pressed and a word scan for any bit changes.

That way the IO CHIP is doing the work instead of CPU.

This should speed up any test for a keypress.

After all CRU bits 6 to 14 are Keyboard bits and looking at these bits is faster then checking all CRU lines.

i.e. 6=binary 110 and 14=binary 1110

Thus if these 4 bits are a 1 you have a key pressed, if all are 0000 then no key.

I don't think I understand that Rich but that would not be the first time. :)

Maybe you mean I can test with separate code and only call KSCAN if there is something waiting?

 

For clarity when I say 1.2 milli-seconds above, I measure like this:

 

1. Read the 9901 timer and remember the number.

 

2. Call KSCAN :   BL @>000E   in ROM  ( with a few setup instructions of course) 

- disable interrupts 

- change workspace

- BL @KSCAN

- Read the byte at >8374 (save it in R4) 

- etc.  you know the drill

 

3. Read the 9901 timer again.

4. Subtract the two readings and get the absolute value. 

 

I get 56 ticks on Classic99 and I just checked again on real iron and it wobbles between 56 and 57.

 

I multiply 56 ticks by 21.3 uS, the tick time of the 9901 timer.

That gives us 1,192.8 uS or about 1.2 mS.

 

I could go faster if I made my own KSCAN code but then I need a translation table for CRU to standard key codes and it gets big real fast.

I want the compiler, the interpreter and disk services to fit into 8K so there is more RAM for programs. :) 

 

Forth Assembler code is in the spoiler for reference.

 

Spoiler

CODE KEY? ( -- ?)  \ *WARNING* it takes 1,128uS for KEY? scan to run
          TOS PUSH,
          TOS CLR,            \ TOS will be our true/false flag
          0 LIMI,
          TOS 837C @@ MOVB,   \ clear GPL flags
          83E0 LWPI,          \ switch to GPL workspace
          R11 83BE @@ MOV,    \ save in empty scratch PAD location
          000E @@ BL,         \ call ROM keyboard scanning routine
          83BE @@ R11 MOV,
          WRKSP0 LWPI,        \ return to Forth's workspace
          837C @@ R1 MOVB,    \ read GPL status byte (=2000 if key pressed)
          R1  3 SLA,          \ check the key bit
          OC IF,              \ if carry flag set
             8374 @@ TOS MOV, \ read the key
          ENDIF,
          2 LIMI,
          NEXT,               \ return
          ENDCODE

 

 

Edited by TheBF
Fixed a wrong comment in the code
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

6 hours ago, TheBF said:

I don't think I understand that Rich but that would not be the first time. :)

Maybe you mean I can test with separate code and only call KSCAN if there is something waiting?

 

Exactly! Check Thierry’s discussion about reading the keyboard.

 

The problem with calling the console’s KSCAN is that it often does a lot more than you want and there are two places it can call the time delay routine. Each time that happens, you lose about 8.5 ms! I think it is used for debouncing, but pretty much everyone agrees that so much time is unnecessary and that using a method other than a fixed delay is superior. There is also GROM access to set an address and then, either to fetch that byte to get the key value for the particular keyboard (3,4,5) in use, or to read two bytes for joystick 0 or 1—may not slow it down that much, but there it is.

 

...lee

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

Yea guys I just think reading the CRU lines 6 to 14 would be faster as you just look for a non zero value for a key pressed.

No key pressed would mean those 4 bits are zero, keypress would be a value depending on that value.

These are checked each time you enable then disable a interrupt.

What you guys are doing seems way more complicated then needed.

 

My point was that would see to me as damn small code and damn fast.

Edited by RXB
missing text
  • Thanks 1
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   1 member

×
×
  • Create New...