-
Posts
4,059 -
Joined
-
Last visited
Content Type
Profiles
Forums
Blogs
Gallery
Events
Store
Community Map
Posts posted by TheBF
-
-
I woke up this morning (my most important job these days) and realized I had not limited the compressed packets to 127 bytes.
This new code fixes that by adding a bytes variable to VDPSKIP and and extra WHILE condition.
It compresses 1K of VDP RAM, filled with the same byte, into 8 cells.
I will change the code in the previous post.
-
3
-
-
I think I have it.
I love these stack string pairs in combination the multiple condition loops for strings.
The secret is to make all the string functions return a new stack string so you can loop the functions until the string is empty.
I made a version of SCAN but for VDP memory. Since /STRING is a code word the loop is pretty quick.
There is a test harness here that puts stuff in VDP RAM. The video shows the speed to do the 342 bytes of test data.
It takes 3.5 seconds to process 2K of font memory on my machine from >800.
Not blazing but not terrible. It compiles in 256 bytes.
Edit: Fixed error that did not limit chunks to 127 bytes.
Spoiler\ vdpencoder.fth compress VDP as run length encoded data in RAM NEEDS DUMP FROM DSK1.TOOLS NEEDS VCOUNT FROM DSK1.VDPMEM \ vdp memory comma allot etc. \ =================[ test setup ]==================== \ compile data into VDP RAM HEX 1000 VP ! \ start of VDP data VHERE 1000 FF VFILL \ init VDP ram so we can see what changed : RPT$, ( char len -- ) 0 DO DUP VC, LOOP DROP ; : V$, ( addr len ) TUCK VHERE VPLACE 1+ VALLOT ; DECIMAL VHERE CHAR A 100 RPT$, CHAR B 125 RPT$, CHAR C 137 RPT$, CHAR D 22 RPT$, CHAR E 10 RPT$, CHAR F 11 RPT$, CHAR G 12 RPT$, CHAR H 13 RPT$, S" This is a string in the middle" V$, CHAR I 19 RPT$, CHAR J 18 RPT$, CHAR K 17 RPT$, CHAR L 16 RPT$, S" This is the second string" V$, CHAR M 15 RPT$, CHAR N 14 RPT$, CHAR O 88 RPT$, VHERE SWAP - CONSTANT VDATA-SIZE .S \ ===================================================== \ ========[ RUN LENGTH ENCODE VDP RAM to LOW RAM ]================== \ low ram memory managers: c, allot etc. HERE : HEAP H @ ; : HALLOT H +! ; : HC, HEAP C! 1 HALLOT ; VARIABLE BYTES \ used to limit the chunks to max 127 bytes \ scan for c until there are no more. Return new stack string : VDPSKIP ( Vaddr len c -- Vaddr' n') >R BYTES OFF BEGIN DUP WHILE \ len<>0 BYTES @ 127 < WHILE OVER VC@ R@ = WHILE 1 /STRING BYTES 1+! REPEAT THEN THEN R> DROP ; : FINDDUPS ( Vaddr len -- NextAddr len Vaddr len) 2DUP OVER VC@ ( -- Vaddr len Vaddr len char ) VDPSKIP ( -- Vaddr len Vddr' len' ) 2SWAP 2 PICK - ; HEX : HRLE, ( Vaddr len -- ) 80 OR HC, VC@ HC, ; : HRLE-$, ( Vaddr len ) TUCK HEAP SWAP VREAD HALLOT ; \ result is a stack string in low RAM. Can save as a program image. : VDP-COMPRESS ( Vaddr len -- Heap len) HEAP >R BEGIN DUP WHILE FINDDUPS ( -- NextAddr len Vaddr len) DUP 2 > IF HRLE, ELSE HRLE-$, THEN REPEAT 2DROP R> H @ OVER - ; HERE SWAP - DECIMAL . .( bytes) \ ================================================== \ reset and erase the heap HEX 2000 H ! HEAP 1000 FF FILL \ HEX 1000 VDATA-SIZE VDP-COMPRESS-
2
-
-
36 minutes ago, Lee Stewart said:
Very nice! I think I can make only one improvement:
: RLE? ( byte -- byte len) DUP 80 AND ;
Because you are not using its value, you don’t need the 0> because any nonzero value tests true.
I am, however, confused about why you used ALIGNED . We’re parsing bytes here, so even addresses should not be a concern, n’est-ce pas?
...lee
You are quite right on both counts.
1. I was being "formal" with 0>.
(well formed expression and all that)
2. The ALIGNED was because the string compiled could be an odd length and my word S, always aligns the address for Forth.
It is not needed if you don't compile VDP data with aligning.
I am noodling on how to make the encoder side in Forth. That's seems to be a bit trickier but I think I can get something going.
-
3
-
-
Over 2 cups of coffee I got this to work.
I don't think I understand all the details of the encoding method because I didn't study it, but I took a stab at making some test data.
In this code the RLETYPE and TYPE could be replaced VMBW and VFILL and it would be pretty fast.
\ run length decoder \ Run Length Encoded Write... \ ++Each run of 3..127 repeated bytes has a length byte with MSb=1 \ followed by the repeated byte. \ ++Each stretch of 1..127 differing (<3 repeats) bytes has a length \ byte followed by that length of bytes. \ NEEDS DUMP FROM DSK1.TOOLS \ test data HEX : RLE, ( char len -- ) 80 OR C, C, ; DECIMAL CREATE RLEDATA ( -- addr) CHAR A 10 RLE, CHAR B 16 RLE, CHAR C 4 RLE, CHAR D 22 RLE, CHAR E 10 RLE, CHAR F 11 RLE, CHAR G 12 RLE, CHAR H 13 RLE, S" This is a string in the middle" S, CHAR I 19 RLE, CHAR J 18 RLE, CHAR K 17 RLE, CHAR L 16 RLE, CHAR M 15 RLE, CHAR N 14 RLE, CHAR O 88 RLE, 0 , \ delimit end of data \ --------------------- HEX : RLE? ( byte -- byte len) DUP 80 AND 0> ; : RLETYPE ( addr len -- addr') 7F AND \ mask the RLE bit gives the repeat# OVER C@ SWAP ( -- addr char len ) 0 DO DUP EMIT LOOP DROP 1+ ; \ bump address ahead : DECODE ( addr -- ) \ type to screen for testing BEGIN COUNT ( addr len ) DUP WHILE CR RLE? ( addr len ?) IF RLETYPE ELSE 2DUP TYPE + ALIGNED \ type and advance address THEN REPEAT 2DROP ;-
3
-
-
Somebody is back at work.
Very neat.
Here is a thought. One of the justifications for having an interpreter is to save space by using higher level "instructions".
As you have heard me say often the darn 9900 is already at the same level as Forth primitives.
Makes me wonder if a Forth version would be smaller albeit slower, but still have an acceptable runtime delay at startup.
: RLD ( src dst len -- ) ... ;
-
9 hours ago, Lee Stewart said:
Perhaps I should move this in-depth DSRLNK discussion to its own topic, seeing as how it is almost exclusively ALC (Assembly Language Code) in nature. We would likely get more participation from ALC and DSR experts who may not be that interested in keeping up with our Forth antics. Thoughts?
...lee
I like it.
-
1
-
-
I went down the rabbit hole of trying to refine that crude VDP screen driver for the metacompiler.
You can ( and I did) waste a lot of time playing with variations on this thing and the conclusion...
wait for it... is that CODE is much faster that Forth.
But is was fun to see how far could push Forth.
The spoiler has the "mostly Forth version.
There are only two code words:
- One to disable interrupts
- VC!+ which made a big difference. It writes a byte to last VDP address set & auto increments the VDP address
You can see that VFILL speed is way slower than ALC but it looks similar to GPL speed.
VFILL is only 16 bytes in Forth versus 28 bytes in ALC. The extra code is in RMODE and WMODE and they are re-useable in other words.
TYPE is acceptable speed when implemented this way.
If I put the FOR NEXT loop counter in a register it would speed up about 10..12% from my testing of FOR NEXT.
So there it is. Stuff you already knew but now you have a video for it.
Spoiler\ STD-OUT1A.FTH output words in Forth + minimal code Sept 17 2023 COMPILER HEX TARGET 8800 CONSTANT VDPRD \ vdp ram read data \ 8802 CONSTANT VDPSTS \ vdp status 8C00 CONSTANT VDPWD \ vdp ram write data 8C02 CONSTANT VDPWA \ vdp ram read/write address \ VDP set-address sub-routines CODE 0LIMI 0 LIMI, NEXT, ENDCODE : RMODE ( vdpaddr -- ) DUP 0LIMI VDPWA C! >< VDPWA C! ; : WMODE ( vdpaddr -- ) 4000 OR RMODE ; VARIABLE C/L COMPILER 20 C/L T! TARGET VARIABLE COL VARIABLE ROW VARIABLE CURSOR VARIABLE C/SCR COMPILER 3C0 C/SCR T! TARGET 20 CONSTANT BL \ : EMIT+ ( c --) VDPWD C! ; \ write & inc. address CODE VC!+ ( c --) TOS SWPB, TOS VDPWD @@ MOVB, TOS POP, NEXT, ENDCODE : VFILL ( vaddr len c -- ) ROT WMODE SWAP FOR DUP VC!+ NEXT DROP ; : >VPOS ( col row -- vaddr) C/L @ * + ; : CURSOR ( -- Vaddr) COL @ ROW @ >VPOS ; : AT-XY ( col row -- ) ROW ! COL ! CURSOR WMODE ; : PAGE ( -- ) 0 C/SCR @ BL VFILL 0 0 AT-XY ; : ?WRAP ( -- ) COL @ C/SCR @ 1- > IF 0 0 AT-XY THEN ; : ROW+! ( n -- ) ROW @ + 23 > IF ROW OFF EXIT THEN ROW ! ; : EMIT+ ( c -- ) VC!+ COL 1+! ?WRAP ; : EMIT ( c --) CURSOR WMODE EMIT+ ; : CR ( -- ) 1 ROW+! COL OFF ; : SPACE ( -- ) BL EMIT ; : TYPE ( addr len -- ) 1- CURSOR WMODE FOR COUNT EMIT+ NEXT DROP ; COMPILER ALSO META DEFINITIONS HOST: ." [CHAR] " PARSE TCOMPILE (S") TS, TCOMPILE TYPE ;HOST IMMEDIATEI am beginning to get this meta compiler organized and it is better than my DOS one.
I have actually learned something after all these years fighting with this stuff.
Here is the test program that runs is the video. It's "normal" Forth with a few magic words to keep the compiler happy.
With the CORE Forth primitives imported from Camel99 Forth, and the output library above it compiles to 1596 bytes.
It could be smaller if I wanted to remove all the primitives that are not used.
\ VFILLTEST.FTH using FOR NEXT loop FOR VFILL and TYPE HEX 2000 ORG \ this must be set before compiling any code INCLUDE DSK7.ITC-FORTH \ preamble for indirect threaded Forth INCLUDE DSK7.STD-OUT1A1 \ uses for/next VFILL IMPORT: ?TERMINAL COMPILER DECIMAL TARGET : VFILLTEST 95 FOR 0 C/SCR @ R@ 33 + VFILL NEXT ; : DELAY ( n -- ) FOR R@ DROP NEXT ; TARGET : MAIN ( -- ) 768 C/SCR ! VFILLTEST 5 12 AT-XY ." VFILL in Forth " 5 13 AT-XY ." FOR DUP VC!+ NEXT " 5000 DELAY 0 0 AT-XY BEGIN ." Hello metacompiling world! " ?TERMINAL UNTIL BYE ; COMPILER AUTOSTART MAIN SAVE DSK7.VFILLTEST3 HOST-
2
-
I know one of you guys has this thing in your garage... but you can't keep it!
The DOD takes a very dim view of people stealing their stuff.
So give it back, OK?
🤓
-
1
-
2
-
-
Your efforts made me stare at mine again today.
Sometimes I feel like I should cut it up into separate CODE words for clarity...
... but then I lay down until the feeling goes away.
-
1
-
2
-
-
18 hours ago, GDMike said:
I see it here
8356 @@ R0 MOV, \ [PAB FNAME] to R0
R0 R9 MOV, \ dup R0 to R9
LOL. Now you are making try to re-understand this thing.
I think the one I was thinking of was somewhere further down.
-
After I failed to get DO/LOOP working I thought I would take a run a something simpler.
Chuck's FOR NEXT loop running on the return stack.
That helped me uncovered what was wrong.
(the order that you load things is critical when you have words with the same name in these @$@!# cross-compilers)
Anyway..
Here is the total code to make a FOR/NEXT loop in ANS Forth.
HEX CODE (NEXT) *RP DEC, \ decrement loop ON RSTACK or R15 OC IF, \ test carry flag *IP IP ADD, \ jump back: add offset value to interpreter pointer NEXT, ENDIF, RP INCT, \ remove counter from Rstack IP INCT, \ move past (LOOP)'s in-line parameter NEXT, ENDCODE : FOR ( n -- ) POSTPONE >R HERE ; IMMEDIATE : NEXT ( -- ) POSTPONE (NEXT) HERE - , ; IMMEDIATEAnd here is what it takes to implement the ANS compliant DO/LOOP
I am now understanding why Chuck was not a big fan and why he abandoned DO/LOOP in his later years.
TARGET CODE <?DO> ( limit ndx -- ) *SP TOS CMP, 1 $ JNE, TOS POP, TOS POP, IP RPOP, NEXT, +CODE <DO> ( limit indx -- ) 1 $: R0 8000 LI, *SP+ R0 SUB, R0 TOS ADD, R0 RPUSH, TOS RPUSH, TOS POP, NEXT, ENDCODE CODE <+LOOP> TOS *RP ADD, TOS POP, 2 $ JMP, +CODE <LOOP> *RP INC, 2 $: 1 $ JNO, IP INCT, 3 $ JMP, 1 $: *IP IP ADD, NEXT, +CODE UNLOOP 3 $: RP 4 AI, NEXT, ENDCODE CODE I ( -- n) TOS PUSH, *RP TOS MOV, 2 (RP) TOS SUB, NEXT, ENDCODE CODE J ( -- n) TOS PUSH, 4 (RP) TOS MOV, \ outer loop index is on the rstack 6 (RP) TOS SUB, \ index = loopindex - fudge NEXT, ENDCODE VARIABLE LP VARIABLE L0 COMPILER 4 CELLS TALLOT TARGET : >L ( x -- ) ( L: -- x ) 2 LP +! LP @ ! ; \ LP stack grows up : L> ( -- x ) ( L: x -- ) LP @ @ -2 LP +! ; : RAKE ( -- ) ( L: 0 a1 a2 .. aN -- ) BEGIN L> ?DUP WHILE POSTPONE THEN REPEAT ; COMPILER ALSO META DEFINITIONS : DO ( n n -- adr) TCOMPILE <DO> 0 >L POSTPONE BEGIN ; IMMEDIATE : ?DO ( n n -- adr) TCOMPILE <?DO> 0 >L POSTPONE BEGIN ; IMMEDIATE : LEAVE ( -- ) TCOMPILE UNLOOP TCOMPILE BRANCH AHEAD >L ; IMMEDIATE \ complete a DO loop : LOOP ( -- ) TCOMPILE <LOOP> <BACK RAKE ; IMMEDIATE : +LOOP ( -- ) TCOMPILE <+LOOP> <BACK RAKE ; IMMEDIATE PREVIOUS DEFINITIONSEdit: copy paste error. I had the hi-level words twice in the file.
-
2
-
-
19 hours ago, GDMike said:
And can be used multiple times in tipi as a separator.
In my old program I would start at pos 40 and count "." All the way left to see how many there were.
Here is the one I am using with lot of comments as I tried to grok it.
I hesitate to offer it since it might add to confusion.
It's written in my cross-compiler Forth it has some odd additions.
I also changed how I manage the workspace and the little name buffer.
If nothing else it might help understand some sections as I changed some of the jumps to structured loops.
And I even found one instruction inside a loop that could be removed to the top of the loop.
CAMEL99-ITC/Compiler/cc9900/SRC.ITC/DSRLINKA.HSF at master · bfox9900/CAMEL99-ITC · GitHub
-
3
-
-
I have finally decided that the simplest way to run this "meta" compiler as they are called in Forth circles, is to IMPORT all the common Forth code primitives.
That way you don't have to do it manually and I don't have to make a program scanner that searches for them in a first pass.
The thing is that without the dictionary headers all these words take about 1K bytes. That's a pretty small run time block.
The current screen output file uses another ?400 bytes.
IMPORT: DUP DROP SWAP OVER ROT -ROT NIP IMPORT: C! C@ COUNT @ ! +! C+! 2! 2@ IMPORT: SP@ SP! RP@ RP! IMPORT: DUP>R >R R> R@ 2>R 2R> IMPORT: ?DUP >< 2DROP 2DUP 2SWAP PICK IMPORT: AND OR XOR IMPORT: 1+ 1- 2+ 2- 2* 4* 8* 2/ IMPORT: 1+! 1-! IMPORT: + - D+ IMPORT: RSHIFT LSHIFT INVERT ABS NEGATE ALIGNED IMPORT: UM* * UM/MOD M/MOD IMPORT: = OVER= 0< U< > < IMPORT: MIN MAX SPLIT FUSE IMPORT: MOVE FILL SKIP SCAN IMPORT: ON OFF IMPORT: BOUNDS /STRING
So I rolled everything up into the ITC-FORTH preamble file. Then you just need to include your I/O file. At the moment there is just STD-OUT.
And optionally you can import some of the other primitives in the system. I used ?TERMINAL in the demo below.
With a different file to define the dictionary headers, I should be able to rebuild the Camel99 kernel on a TI-99!
That might be a first? A language rebuilding itself on the 99.hello world looks like this. I think it's turning into a useable thing.
\ HELLO.FTH for the recompiler. Demo Sep 16 2023 Fox HEX 2000 ORG \ this must be set before compiling any code INCLUDE DSK7.ITC-FORTH \ preamble for indirect threaded Forth INCLUDE DSK7.STD-OUT IMPORT: ?TERMINAL COMPILER DECIMAL TARGET : MAIN ( -- ) 768 C/SCR ! \ init this variable PAGE S" HELLO WORLD" TYPE BEGIN ?TERMINAL UNTIL BYE ; \ tell the compiler what to do with this COMPILER AUTOSTART MAIN SAVE DSK7.HELLOWORLD HOST \ return to HOST Forth \ you could automatically exit to TI-99 Main page ( BYE )-
2
-
-
3 hours ago, Lee Stewart said:
One of the changes I am seriously considering for fbForth 3.0 is revamping the file management system—especially, how DSRLNK works. I am looking into an implementation of Paolo Bagnaresi’s version as modified by Bill R. Sullivan. I am not sure I want to include Bill’s optional use of CPU RAM PABs and buffers, but I definitely want to use the 5 saved parameters after the first call to DSRLNK so CRU and device/program searches can be minimized with subsequent DSRLNK calls. Whereas Bill suggests a stack for multiple open files, I am thinking of adding those parameters to the head of each PAB.
I am also considering using a linked list of PABs, much as TI Basic does—we’ll see.
...lee
I like where you are going.
I have considered adding fields to the PAB as well. I also feel like there are things that could better use Forth.
If you pull code that finds the DOT in the device string and replace it with SCAN in ALC you get a reusable word and no speed reduction.
I am not sure which 5 parameters you are referring to but the one that seems to be missing in a standard PAB is the address of the "." character.
I call that the "real pab address" since it is used to reference the pab if I recall correctly.
-
3
-
-
Well as these things seem to go, I had trouble getting my DO LOOP code to work after it "recompiled" as in... it didn't work.
That didn't seem obvious to fix so I moved ahead with getting text on the screen.
This brought me back to the VDP and video screen control. I decided to simplify and roll the two together.
I changed the name VC!+ to EMIT+ because that's what it does.
and I remembered a magic word called DUP>R
This improves the speed of loops with a counter on the return stack.
Here is the STD-OUT.FTH file. It is pretty reasonable in terms of size for what it does.
TYPE is adequately fast but VFILL would be a good candidate to recode in Forth Assembler of course.
Spoiler\ Standard Forth output words COMPILER HEX TARGET 8800 CONSTANT VDPRD \ vdp ram read data \ 8802 CONSTANT VDPSTS \ vdp status 8C00 CONSTANT VDPWD \ vdp ram write data 8C02 CONSTANT VDPWA \ vdp ram read/write address \ VDP set-address sub-routines CODE 0LIMI 0 LIMI, NEXT, ENDCODE : RMODE ( vdpaddr -- ) DUP 0LIMI VDPWA C! >< VDPWA C! ; : WMODE ( vdpaddr -- ) 4000 OR RMODE ; : EMIT+ ( c --) VDPWD C! ; \ write & inc. address : VFILL ( vaddr len c -- ) ROT WMODE SWAP >R BEGIN DUP EMIT+ R> 1- DUP>R -UNTIL R> 2DROP ; VARIABLE C/L COMPILER 20 C/L T! TARGET VARIABLE COL VARIABLE ROW VARIABLE CURSOR VARIABLE C/SCR COMPILER 3C0 C/SCR T! TARGET 20 CONSTANT BL : CLIP ( n lo hi -- n) ROT MIN MAX ; : >VPOS ( col row -- vaddr) C/L @ * + ; : CURSOR ( -- Vaddr) COL @ ROW @ >VPOS 0 C/SCR @ CLIP ; : COL+! ( n -- ) COL @ + DUP C/SCR @ > IF DROP COL OFF EXIT THEN COL ! ; : ROW+! ( n -- ) ROW @ + 0 23 CLIP ROW ! ; : EMIT ( c --) CURSOR WMODE EMIT+ 1 COL+! ; : CR ( -- ) 1 ROW+! COL OFF ; : SPACE ( -- ) BL EMIT ; : TYPE ( addr len -- ) CURSOR WMODE >R BEGIN COUNT EMIT+ 1 COL+! R> 1- DUP>R -UNTIL R> 2DROP ; : AT-XY ( col row -- ) ROW ! COL ! CURSOR WMODE ; : PAGE 0 C/SCR @ 20 VFILL 0 0 AT-XY ;So with that working I figured out how to make S" work in a cross-compiled definition.
That required more spells and elixirs than I bargained for but now I know how to do it.
I tested it in this "hello world" program which is back to looking like alphabet soup.
I will migrate S" et al back into the compiler I think, but I will need a DEFER word to handle (S")
The program compiles to 770 bytes because of the extra features in the STD-OUT file which also required a lot of primitives to be "imported"
But on the plus side the actual MAIN program is normal Forth code
It looks like I need to make IMPORT: smarter so it only loads primitives that are not already loaded.
Then I can put import statements in the library files and forget about them.
Spoiler\ TESTPROG2.FTH Demo IMPORT: CODE loops and AUTOSTART Sep 2023 Fox HEX 2000 ORG \ this must be set before compiling any code INCLUDE DSK7.ITC-FORTH \ preamble for indirect threaded Forth \ extend the cross-compiler 1st COMPILER ALSO META DEFINITIONS ( this holds the "immediate" words and support ) HOST: TALIGN ( -- ) THERE ALIGNED H ! ;HOST HOST: S, ( c-addr u -- ) THERE OVER 1+ TALLOT PLACE TALIGN ;HOST \ steal needed kernel primitives COMPILER WARNINGS OFF IMPORT: DUP 2DUP SWAP DROP 2DROP OVER >< ROT IMPORT: >R R> DUP>R IMPORT: 1- 1+ 0= * + > IMPORT: C@ C! COUNT @ ! IMPORT: OR FUSE SPLIT IMPORT: ON OFF MIN MAX ALIGNED COMPILER WARNINGS ON HEX INCLUDE DSK7.STD-OUT COMPILER DECIMAL TARGET : (S") ( -- c-addr u) R> COUNT 2DUP + ALIGNED >R ; \ run-time for S" COMPILER ALSO META DEFINITIONS HOST: S" [CHAR] " PARSE TCOMPILE (S") S, ;HOST IMMEDIATE TARGET : MAIN ( -- ) 768 C/SCR ! PAGE S" HELLO WORLD" TYPE BEGIN AGAIN ; COMPILER AUTOSTART MAIN SAVE DSK7.HELLOWORLD
-
1
-
-
After a break and some food I have to recant my evil ways.
DO LOOP makes the whole thing so simple.
HEX 8800 CONSTANT VDPRD \ vdp ram read data 8802 CONSTANT VDPSTS \ vdp status 8C00 CONSTANT VDPWD \ vdp ram write data 8C02 CONSTANT VDPWA \ vdp ram read/write address \ VDP set-address sub-routines CODE 0LIMI 0 LIMI, NEXT, ENDCODE : RMODE ( vdpaddr -- ) DUP 0LIMI VDPWA C! >< VDPWA C! ; : WMODE ( vdpaddr -- ) 4000 OR RMODE ; : VC@+ ( Vdpaddr -- c) VDPRD C@ ; \ read & inc. address : VC!+ ( c --) VDPWD C! ; \ write & inc. address : VC@ ( VDP-adr -- char ) RMODE VDPRD C@ ; : VC! ( c vaddr --) WMODE VC!+ ; \ set address and write \ VDP integer fetch & store : V@ ( VDPadr -- n) VC@ VC@+ FUSE ; : V! ( n vaddr --) >R SPLIT R> VC! VC!+ ; : VWRITE ( addr Vaddr cnt -- ) SWAP WMODE 0 DO COUNT VC!+ LOOP DROP ; : VFILL ( Vaddr cnt char --) ROT WMODE SWAP 0 DO DUP VC!+ LOOP DROP ; : VREAD ( Vaddr Ram cnt --) ROT RMODE BOUNDS DO VC@+ I C! LOOP DROP ;
-
2
-
-
Once you have the VDP driver you can make some standard output words.
I am testing all this on Camel Forth before making it a library for the recompiler project.
I will probably have to relent and use Assembler for the VDP library to make work better but this was a fun exercise.
Spoiler\ Standard Forth output words VARIABLE C/L C/L@ C/L ! VARIABLE COL VARIABLE ROW VARIABLE CURSOR VARIABLE C/SCR 3C0 C/SCR ! 20 CONSTANT BL : >VPOS ( col row -- vaddr) C/L @ * + ; : CLIP ( n lo hi -- n) ROT MIN MAX ; : CURSOR ( -- Vaddr) COL @ ROW @ >VPOS 0 C/SCR @ CLIP ; : COL+! ( n -- ) COL @ + DUP C/SCR @ > IF DROP COL OFF EXIT THEN COL ! ; : ROW+! ( n -- ) ROW @ + 0 23 CLIP ROW ! ; : EMIT ( c --) CURSOR VC! 1 COL+! ; : CR ( -- ) 1 ROW+! COL OFF ; : SPACE BL EMIT ; : TYPE ( addr len -- ) >R BEGIN COUNT EMIT R> 1- DUP >R -UNTIL R> 2DROP ; : AT-XY ( col row -- ) ROW ! COL ! CURSOR WMODE ; : PAGE 0 C/SCR @ 20 VFILL 0 0 AT-XY ; : VDPTYPE ( addr len --) TUCK CURSOR SWAP VWRITE COL+! ;It not FAST doing it this way, putting a slooooow EMIT in a loop, but it works.
(PAGE is painful to watch)
But the text writing speed is not bad if you use the block write word VDPTYPE as seen in the 2nd video
Test programs
: TEST PAGE 100 BEGIN S" HELLO WORLD! " TYPE 1- DUP -UNTIL DROP ; : TEST2 PAGE 40 BEGIN S" Hello World! " VDPTYPE 1- DUP -UNTIL DROP ;-
2
-
-
So I wondered what would happen if I wrote an entire VDP driver in Forth? 🙂
It's not fast. That for certain.
But to satisfy everyone's curiosity here is a set of VDP words written entirely in Forth except of a code word to turn off the interrupts.
I added -UNTIL (not until) which is something Chuck invented for Machine Forth because it is faster than 0= UNTIL.
I chose to try it without DO/LOOP first because DO LOOP adds some runtime code to the program.
It's pretty tidy, but it would benefit greatly from a code word like R+! or R1-! to manage the counter on the return stack
As I think about it adding a machine Forth style FOR/NEXT loop would be trivial with R1-! in the system.
: -UNTIL POSTPONE WHILE POSTPONE REPEAT ; IMMEDIATE HEX 8800 CONSTANT VDPRD \ vdp ram read data 8802 CONSTANT VDPSTS \ vdp status 8C00 CONSTANT VDPWD \ vdp ram write data 8C02 CONSTANT VDPWA \ vdp ram read/write address \ VDP set-address sub-routines CODE 0LIMI 0 LIMI, NEXT, ENDCODE : RMODE ( vdpaddr -- ) DUP 0LIMI VDPWA C! >< VDPWA C! ; : WMODE ( vdpaddr -- ) 4000 OR RMODE ; : VC@+ ( Vdpaddr -- c) VDPRD C@ ; \ read & inc. address : VC!+ ( c --) VDPWD C! ; \ write & inc. address : VC@ ( VDP-adr -- char ) RMODE VDPRD C@ ; : VC! ( c vaddr --) WMODE VC!+ ; \ set address and write \ VDP integer fetch & store : V@ ( VDPadr -- n) VC@ VC@+ FUSE ; : V! ( n vaddr --) >R SPLIT R> VC! VC!+ ; : VWRITE ( RAM-addr VDP-addr cnt -- ) SWAP WMODE >R BEGIN COUNT VC!+ R> 1- DUP >R -UNTIL R> 2DROP ; : VREAD ( Vaddr Ram cnt --) >R SWAP RMODE BEGIN VC@+ OVER C! R> 1- DUP >R -UNTIL R> 2DROP ; : VFILL ( vaddr cnt char --) SWAP >R SWAP WMODE BEGIN DUP VC!+ R> 1- DUP >R -UNTIL R> 2DROP ;-
1
-
1
-
-
So now that the compiler works correctly I took some time to improve the user experience.
- As mentioned I consolidated all the preamble into a file called ITC-FORTH. So you just include that file before any other code is compiled.
- I changed IMPORT: so it knows to search in the CAMEL99 Forth dictionary for primitive words. Before you had to manually add Forth to the search order.
- I add the command AUTOSTART so it is simple to specify the routine that will run after COLD builds the Forth stacks and sets the workspace.
And with that here is modestly complicated "recompiled" Forth program that compiles and runs.
\ TESTPROG2.FTH Demo IMPORT: CODE loops and AUTOSTART Sep 2023 Fox HEX 2000 ORG \ this must be set before compiling any code INCLUDE DSK7.ITC-FORTH \ preamble for indirect threaded Forth IMPORT: DUP DROP 1- 0= COMPILER HEX TARGET CODE BYE ( --) 0 LIMI, 0 @@ BLWP, NEXT, ENDCODE : LOOP2 ( -- ) 5000 BEGIN 1- DUP 0= UNTIL DROP ; : LOOP1 ( -- ) 5000 BEGIN 1- DUP WHILE REPEAT DROP ; : MAIN ( -- ) LOOP1 LOOP2 BYE ; COMPILER AUTOSTART MAIN SAVE DSK7.TEST2
That's enough for one day. I am behind on my Viola practicing.
-
2
-
No matter how many times I do these Forth compiler things I always get something messed up initially.
I guess if it was easy everybody would be doing it.
I had an error in what I compiled into the program when ';' is encountered but thanks to the Classi99 debug window it was easy to see.
I had forgotten that EXIT must be a proper CODE word so the Forth compiler can"compile" it correctly.
I initially gave it a label like DOVAR DOCON etc.
The new file is here: RECOMPILER/src/EXECUTORS.FTH at main · bfox9900/RECOMPILER · GitHub
Here is the little program I used to test "nested" colon definitions and it works as advertised now. It compiles to 176 bytes.
Something to be aware of is that these programs have no code in scratchpad RAM currently.
So they will run ~20% slower than Camel99 Forth. But that can be fixed later.
I am slowly learning how to simplify the "noise" in the code.
I realized that COLD just needs to be a label since I branch directly into it at startup.
I think I will wrap up the first 26 lines into a "PREAMBLE" file that sets up the program.
Things I think I like:
- The Virtual machine is a separate file that you load first
- The branch and loop compilers are a separate file
- The data description words are a separate file
In "theory" this means we could change the threading type to direct-threaded with very little trouble. 😇
So with this level of compiler working I should be able to make a little VDP I/O library and write HELLO world next.
I played too many games with Camel99 VDP I/O to allow it to imported as is.
(I used a BL sub-routine to set VDP addresses which is not relocatable)
No worries. I can make something smaller if I don't worry about speed.
\ NESTTEST.FTH test nested calls COMPILER NEW HEX 2000 ORG WARNINGS OFF INCLUDE DSK7.EXECUTORS \ load EXIT DOCOL DOVAR ETC. INCLUDE DSK7.BRANCHING \ compilers: IF THEN BEGIN AGAIN... INCLUDE DSK7.ITCTYPES \ CONSTANT VARIABLE : ; etc. WARNINGS ON COMPILER HEX TARGET VARIABLE BOOT \ hold the cfa of the word that boots from COLD L: COLD \ COLD runs at boot time to build the Forth VM 8300 LWPI, \ set 9900 workspace SP 83FE LI, \ data stack in scratchpad RP 83D0 LI, \ return stack in scratchpad IP BOOT LI, \ load interpreter pointer with boot word R10 _NEXT LI, \ inner interpreter stays in R10 *R10 B, \ jump into the interpreter TARGET ALSO FORTH IMPORT: DUP DROP 1- TARGET CODE BYE 0 LIMI, 0 @@ BLWP, ENDCODE : SUB3 BEEF ; : SUB2 SUB3 ; : SUB1 SUB2 ; : MAIN 4000 BEGIN 1- DUP WHILE SUB1 DROP REPEAT DROP BYE ; COMPILER HEX COLD 2002 T! \ jumps into cold on startup T' MAIN BOOT T! \ set the boot variable SAVE DSK7.NESTTEST-
1
-
Now I remember why I never tried to completely re-write my cross-compiler. My head is spinning.
Last Wednesday I got the import utility working. It as taken me until now, working on and off to get this re-compiler to work.
I just had to get it to build a program and see it run.
Here is the first recompiled program with all the magic incantations it currently takes to make it go.
I will work on improving the noise but it's good in the beginning to understand what is happening.
I also have see if my relocation method in Machine Forth can be squeezed into service here.
Right now the programs are AORG >2000 only.
The demo starts and runs BYE.
It is real indirect threaded code but there is no dictionary or interpreter or compiler included in the program.
That is all provided by the "HOST" Forth. It compiles to 122 bytes.
\ TEST PROGRAM 1 COMPILER NEW HEX 2000 ORG WARNINGS OFF INCLUDE DSK7.EXECUTORS \ load EXIT DOCOL DOVAR ETC. INCLUDE DSK7.BRANCHING \ compilers: IF THEN BEGIN AGAIN... INCLUDE DSK7.ITCTYPES \ CONSTANT VARIABLE : ; ETC. WARNINGS ON TARGET ALSO FORTH \ import needs to see Forth to find kernel primitives IMPORT: + COMPILER HEX TARGET VARIABLE BOOT \ hold the cfa of the word that boots from COLD \ bye does not end with NEXT so we can' import it CODE BYE 0 LIMI, 0 @@ BLWP, NEXT, ENDCODE CODE COLD \ COLD is a key primitive that builds the Forth VM 8300 LWPI, \ set 9900 workspace SP 3FFE LI, \ data stack RP 3FE0 LI, \ return stack IP BOOT LI, \ load interpreter pointer with boot word R10 EXIT CELL+ LI, \ EXIT + 2 = NEXT -> R10 *R10 B, \ jump to NEXT (inner interpreter) ENDCODE TARGET : MAIN BYE ; COMPILER HEX T' MAIN BOOT HOST ! \ set the boot variable COMPILER T' COLD >BODY 2002 HOST ! SAVE DSK7.TESTPROG1For the masochists in the group you can see the source code here and the bin folder has everything in TI-99 format.
Here is a little video showing the process. The program is totally "under-whelming" but its a start.
-
3
-
-
Over in another thread @InsaneMultitasker posted some code to detect the presence of a SAMS Card.
I am always curious about comparing Assembly Language to Forth.
In this case, at least for Camel99 Forth, a couple of extra words are needed from the SAMS library to do this in Forth.
For the Forth curious, here is what I came up with. (un tested at time of posting)
Edit: Tested and it works.
But SAMSINI in the DSK1.SAMS file runs when the file compiles and it burps with an error if SAMS is not present.
Again I see the low level semantic power of 9900 Assembly language is about the same as Forth primitives.
The gap widens only when we begin making higher level words that we can concatenate to make yet higher level words.
Original code
H0101 data >0101 SAMSDT DATA PLAYWS,$+2 clr @0(R13) li r12,>1E00 sbo 0 mov @>4008,R2 ;save whatever was in the mapper for >4000-4fff a @H0101,@>4008 ;change the value c r2,@>4008 ;is it still the same? jeq noams ;yes. can't be SAMS seto @0(R13) ;no, assume SAMS mov r2,@>4008 ;restore, for futureproofing noams sbz 0 rtwp
A Forth version\ For Camel99 Forth NEEDS 'R12 FROM DSK1.SAMS HEX : SAMSDT ( -- ?) \ returns true FLAG if present 1E00 'R12 ! \ set CRU to SAMS card 0SBO \ Enable the card 4008 @ >R \ save whatever was in the mapper on RETURN stack 0101 4008 +! \ change the value R@ 4008 @ <> \ is it different? (compared to top Rstack) R> 4008 ! \ restore previous value to SAMS card 0SBZ \ disable the card ;
-
2
-
-
6 minutes ago, FarmerPotato said:
For more historical context: check "Image Based Persistence" under Wikipedia Smalltalk article. Citation 37 looks like a whole (online) book.
Smalltalk images are similar to (restartable) core dumps
BSAVE 🙂Yes Forth systems work the same way. I have the function in a word called SAVESYS ( OR if you run Forth from SUPER CART I call it SUPERSAVE)
I didn't use the name BSAVE because that has historical usage for saving a Forth system in BLOCKS.
Here is the code and an example of a minimal save start up word call GO. WARM inits Forth and ABORT restarts the interpreter.
This newest version saves the program and if LOW RAM has been used ( H variable <> 0) it saves that too as a separate program file
with the appropriate header for the E/A5 loader to pull it in.
One day I will add saving VDP RAM and SAMS. VDP is simplest because I have a VDP memory management variable in the kernel (VP) so it will work like LOW RAM.
SpoilerCR .( SAVESYS.FTH V2 creates EA5 program Dec 2022 B Fox) \ creates a binary program E/A 5 format. \ Makes as many files as needed to save the system \ Jun 2022 version fixed section overlap. Tested with check sum. \ Dec 2022 saves the HEAP (Low RAM) as a file if variable H <> 0 \ Usage example: \ INCLUDE DSK2.MYPOGRAM ( load all your code) \ : STARTUP WARM CR ." Myprogram ready" ABORT" ; \ LOCK ( this locks the dictionary to the current size ) \ \ INCLUDE DSK1.SAVESYS \ ' STARTUP SAVESYS DSK3.MYFILENAME \ NEEDS DUMP FROM DSK1.TOOLS NEEDS LOCK FROM DSK1.MARKER NEEDS LOAD-FILE FROM DSK1.LOADSAVE \ we use SAVE-FILE from this library NEEDS U.R FROM DSK1.UDOTR HERE HEX A000 CONSTANT HIMEM \ start of Camel99 Forth program in CPU RAM 1000 CONSTANT VDPBUFF \ Programs write to file from VDP Ram 2000 CONSTANT LOWRAM 2000 CONSTANT 8K 8K 3 CELLS - CONSTANT IMGSIZE \ makes space for header cells 13 CONSTANT PROGRAM \ file mode for Program files \ define the file header fields. *THESE ARE VDP ADDRESSES* VDPBUFF CONSTANT MULTIFLAG VDPBUFF 1 CELLS + CONSTANT PROGSIZE VDPBUFF 2 CELLS + CONSTANT LOADADDR VDPBUFF 3 CELLS + CONSTANT CODEBUFF \ COPY 8K program chunks to here 3 CELLS CONSTANT HEADLEN : HEADER ( Vaddr size ?) \ store header info in VDP RAM MULTIFLAG V! PROGSIZE V! LOADADDR V! ; : END ( -- addr ) ORGDP @ DUP C000 < IF HONK CR ." WARNING: missing LOCK directive" THEN ; \ words to compute Forth system properties : SYS-SIZE ( -- n) HIMEM END SWAP - ; : #FILES ( -- n) SYS-SIZE 8K /MOD SWAP IF 1+ THEN ; : CODECHUNK ( n -- addr) IMGSIZE * HIMEM + ; : CHUNKSIZE ( n -- n ) CODECHUNK END SWAP - IMGSIZE MIN ; : LASTCHAR++ ( Caddr len --) 1- + 1 SWAP C+! ; : HEAPSIZE ( -- n) H @ LOWRAM - ; : ?PATH ( addr len -- addr len ) 2DUP [CHAR] . SCAN NIP 0= ABORT" Path expected" ; : GET-PATH ( <text>) BL PARSE-WORD ?PATH PAD PLACE ; : FILENAME ( -- addr len) PAD COUNT ; VARIABLE FILECOUNT : SAVE-IMAGE ( addr len Vaddr size -- ) CR ." Writing file: " FILENAME TYPE HEADLEN + PROGRAM SAVE-FILE FILENAME LASTCHAR++ FILECOUNT 1+! ; : SAVELO ( -- ) HEAPSIZE IF LOWRAM HEAPSIZE DUP>R FALSE HEADER \ heap is last file saved LOWRAM CODEBUFF R@ VWRITE \ copy HEAP to VDP FILENAME VDPBUFF R> SAVE-IMAGE THEN ; HEX : SAVEHI ( XT -- <textpath> ) #FILES 0 ?DO \ compute file header values I CODECHUNK I CHUNKSIZE ( -- addr size ) I 1+ #FILES <> HEAPSIZE 0> OR \ multiflag=true if heap has data ( addr size ?) HEADER \ store in file header \ Copy to VDP RAM LOADADDR V@ CODEBUFF PROGSIZE V@ HEADLEN + VWRITE \ write VDP to disk" FILENAME VDPBUFF PROGSIZE V@ SAVE-IMAGE LOOP ; : .BYTES&ADDR ( addr size --) DECIMAL 5 U.R ." bytes, at " HEX ." >" 4 U.R ; : REPORT CR CR ." Himem : " HIMEM ORGDP @ OVER - .BYTES&ADDR CR ." Heap : " LOWRAM HEAPSIZE .BYTES&ADDR CR ." Saved in " FILECOUNT @ . ." EA5 files" CR ; : SAVESYS ( xt -- <path>) BOOT ! FILECOUNT OFF GET-PATH SAVEHI SAVELO REPORT ; HERE SWAP - CR DECIMAL . .( bytes) \ ---------------- \ TEST CODE INCLUDE DSK1.MALLOC HEX 800 MALLOC CONSTANT MYBUFFER \ mybuffer is in Low RAM MYBUFFER 800 CHAR $ FILL : GO WARM ABORT ; \ minimum startup code to start Forth interpreter LOCK \ lock dictionary to current size on re-boot ' GO SAVESYS DSK7.TESTKERNEL-
3
-
-
9 hours ago, FarmerPotato said:
This is fascinating. I've felt stuck over how to "package" a Forth (game) without all the compiler stuff.
Smalltalk approached this problem with a "packager", but Smalltalk's runtime flexibility made it hard to predict what was used and what wasn't.
Could you compile the dictionary to a dummy address? Say, at >6000 for a ROM cartridge. Like, a game written in Forth.
Assuming 32K RAM required. Variables would need to point to RAM. Maybe you could have a defining word PADVARIABLE? ALLOT would consume 32K RAM.
I'm also recalling Lee's fbForth cartridge--where names are not mixed in with the code.
A really crazy idea I just had is: your IMPORT: is kind of like an assembly REF. What if the re-compilation made "relocatable" code containing unresolved references?
I see this is completely unnecessary if compilation just uses a dummy base address, say 6000, while actually building the image in RAM elsewhere.But: Analogous to the TI assembler, a re-compiler would build a REF/DEF table. A REF table entry is a linked list of everywhere the symbol is needed. Each new word gets a DEF with its address. (All addresses to be relative to a base.)
The recompiler could even infer the IMPORT: for any word not yet encountered. (Not yet present in the DEF table.)
Again just like the E/A loader: at load/link time, there is a base address, like 6000 or A000, and DEFs are calculated to be absolute addresses. REFs are then resolved to the corresponding DEF.
Probably an overly complicated solution.
Anyway, your work here is a big step toward solving the "packaging" problem!I love your excitement.
You have touched on a bunch of possibilities. I am going to get something working and then start to think about where it goes
For example it is relatively simple to change the threading method from indirect threading to direct threading or even sub-routine threading with inlining.
The infer idea could possibly be done with a first pass through the source code and compile the run time code for all the primitives that are required.
That might be overkill however since it's pretty simple to add primitives to an import statement.
I am thinking about adding an import statement to every source file and make IMPORT: a bit smarter so it only compiles a primitive once.
Lots to think about. And yes I can compiler headers in other memory because I can move the Forth dictionary to other memory.
I am also considering putting the compiled code into VDP RAM so that I can make programs bigger than 8k, but that will mean putting some of the compiler in SuperCart Ram or SAMS.
BTW the way I think I will "borrow" that word 'PACKAGE' for something.
-
3
-


Benchmarking Languages
in TI-99/4A Development
Posted
Hey thanks for this. Great to see a result.