+TheBF Posted September 5, 2018 Author Share Posted September 5, 2018 (edited) 28649 (>6FE9) is, indeed, prime, but >6FE5 (28645) is not. It is the latter that TI used. The number (>7AB9) TI used to add to the product of >6FE5 and the value at >83C0 is not prime either. They do not have any primes in common, however. I always thought the bit pattern of the two numbers might be important, but I am not sure. It is kind of interesting that the second number is π to 5 places with 1 added to the last place—as though to insure the lowest bit is 1. In fact the lowest bit of both numbers is 1. ...lee Good catch. Moving too fast this morning. I was looking for a close prime number to 6FE5 for the experiment. So the evidence is point to the fact the choosing these internal numbers is important. Edited September 5, 2018 by TheBF Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 5, 2018 Author Share Posted September 5, 2018 Here is a much faster version of your PRNG-testing program that avoids copying the screen from VRAM to CRAM every iteration: \ Random Screen Fill that avoids using SCAN and whole-screen copying. VARIABLE BLANK_CNT VARIABLE ITERATIONS VARIABLE DUPLICATES : WAIT-KEY BEGIN KEY? UNTIL ; : UNTILFULL ( -- ) PAGE C/SCR @ BLANK_CNT ! \ initialize to screenfull of blanks DUPLICATES OFF ITERATIONS OFF BEGIN C/SCR @ RND DUP VC@ DUP BL = IF DROP [CHAR] A SWAP VC! -1 BLANK_CNT +! \ decrement blank count ELSE 1+ SWAP VC! 1 DUPLICATES +! THEN 1 ITERATIONS +! BLANK_CNT @ 0= \ did we hit all the blanks? UNTIL BEEP WAIT-KEY PAGE ." Random Screen Fill" CR CR ITERATIONS @ U. ." iterations" CR DUPLICATES @ U. ." duplicates" ; and here it is converted to fbForth: \ Random Screen Fill converted from CAMEL99 to fbForth 2.0--- \ ...avoids using SCAN and whole-screen copying 0 VARIABLE BLANK_CNT 0 VARIABLE ITERATIONS 0 VARIABLE DUPLICATES : WAIT-KEY BEGIN ?KEY UNTIL ; : UNTILFULL ( -- ) PAGE SCRN_END @ BLANK_CNT ! \ initialize to screenfull of blanks 0 DUPLICATES ! 0 ITERATIONS ! BEGIN SCRN_END @ RND DUP VSBR DUP BL = IF DROP ASCII A SWAP VSBW -1 BLANK_CNT +! \ decrement blank count ELSE 1+ SWAP VSBW 1 DUPLICATES +! THEN 1 ITERATIONS +! BLANK_CNT @ 0= \ did we hit all the blanks? UNTIL BEEP WAIT-KEY PAGE ." Random Screen Fill" CR CR ITERATIONS @ U. ." iterations" CR DUPLICATES @ U. ." duplicates" ; ...lee Not only is it faster, it seems to be accurate. Back to people management I go. Thanks Lee. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 5, 2018 Author Share Posted September 5, 2018 Here is the little program I use to check the repetition frequency of the PRNG under test. It uses a 32bit counter because I didn't want to have an overflow. CREATE DCNT 0 , 0 , \ 32bit integer variable, 4 bytes : DCNT++ ( -- ) DCNT 2@ 1 M+ DCNT 2! ; \ incr. 32 bit counter : CLRCNT ( -- ) 0 0 DCNT 2! ; \ clear counter : UD. ( d -- ) <# #S #> TYPE SPACE ; \ print double, unsigned : .DCNT ( -- ) DCNT 2@ UD. ; \ print counter : NEXTREP \ find next repetition of Random number CLRCNT CR ." Searching for:" RNDW DUP U. CR BEGIN DCNT++ RNDW OVER = UNTIL CR ." Duplicate after " .DCNT ." random no.s" ; If this is working as I expect, which under Lee's watchful eye am learning is not always the case, the TI Forth PRNG repeats after 20,000 or so and the GForth method shows 65,536 before a duplicate. I think using the circular shift limits the TI Forth method, but again, I don't have the math to prove my intuition. Comments welcome. I would never have suspected the intricacies of a PRNG were so great. I should probably have looked up the current state of the art on testing... oh well, it's a hobby after all. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 7, 2018 Author Share Posted September 7, 2018 Handle Base File Access Change A while back I discovered a problem with my ANS/ISO File system not selecting properly from one file handle to another file handle. This is required for example if you wanted to copy data from one file to another file. The files opened correctly, the code was selecting the correct PAB for each open file, but I was not changing the O/S address >8356 (which is called DSRNAM from what I can discover) I did a kludge fix that involved string processing (yuk!) but the real fix meant adding a new field to the standard PAB. Per the E/A Manual the standard PAB in CAMEL99 Forth is: byte : opcode byte : flag/status cell : data buffer address byte : record length byte : character count (read or written) cell : record number byte : screen offset (unused) string: filename (byte counted string) MAX length in CAMEL99=32 bytes CAMEL99 now has this new field cell: DSRNAM The magic number that the O/S needs in DRSNAM to "select" a file, is the VDP address of the filename string after the '.' character. I was doing that correctly when I created the PAB, but the number was not stored anywhere for fast retrieval. In version 2.0.23 I have added a DSRNAM field to the PAB after the file name buffer. When selecting via file handle it is now very simple. The word SELECT takes the handle and "selects" the correct VDP PAB address from an array of PABs. Now it also reaches into the PAB to retrieve the DSR name and put it into >8356 Here is the new code for SELECT with extra comments. : SELECT ( hndl -- ) DUP ?FILES \ test if hndl<= max files ]PTAB @ DUP 0= ABORT" Cannot select hndl 0" \ ]PTAB is array of PAB addresses ^PAB ! \ store correct PAB in pab pointer [PAB DSRNAM] V@ DSRNAM ! ; \ fetch dsrnam from VDP ram, store in CPU ram Once I made the decision to alter the PAB structure the rest went quickly. It will get up on GITHUB this weekend. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 12, 2018 Author Share Posted September 12, 2018 (edited) The Ultimate Cure for Your Insomnia The latest version of the manual for Camel99 Forth is up on GitHub. https://github.com/bfox9900/CAMEL99-V2/tree/master/DOCS (Edit: UPDATED link to show current doc) The improvements include Reorganizing sections for clarity Expanded discussion on programming tools and disk utilities Expanded discussion on using Forth Assembler including the use of BLWP from within Forth Expanded discussion on multi-tasking And oh so much more! It's now 158 pages of fun fun fun! That is all. Edited April 4, 2019 by TheBF 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 17, 2018 Author Share Posted September 17, 2018 30 Tasks + the Console Running on Camel99 Forth I have wanted to trying this for a while on this little system. I always envisioned the multi-tasker to be able to "spawn" a new task. That means I need to be able to allocate some memory from somewhere and then setup a new task in that memory block. In version 2 of the CAMEL99 I wanted a low overhead way to use the low-memory block on the expansion RAM so I did what any Forther would do and turned the memory into a manually controlled stack of memory. Anytime you need some memory you say " n MALLOC" where n is the number of bytes. Later you can say "n MFREE" to give it back. It's very crude. You can't remove a memory block from the middle of the stack alhough you could re-used it. Anyway, armed with that I realized I could create as many tasks as the low-memory could hold, up until it smashed into the Forth stacks at the top of low memory. I can get a maximum of 32. Each task needs 192 bytes and that is called the USER Area. The constant USIZE returns 192 FORK is routine that simply copies the current USER area into a new USER area and sets the 2 stack pointers to the new space. ASSIGN give the task the address of a program to run WAKE sets a flag in the task that it should run. Using the simple MALLOC the routine to SPAWN a new task, with a program passed to it to run, is very simple. \ create a task in heap, fork it, assign Execution token and run it : SPAWN ( xt -- ) USIZE MALLOC ( pid) DUP >R FORK R@ ASSIGN R> WAKE ; The spoiler has the demo code which just puts the numbers >DEAD >BEEF on the local stack of each task and increments a global variable called X. The 30tasks video shows the me typing at the console starting everything up and killing it all and doing it again. You can see how much the console slows down with 30 jobs running. Each task is set to behave "NICE"ly and not hogging the system too much. (NICE is a UNIX command that nobody uses. You lookup why) The lowmemtasks video just shows the CLASSIC99 debugger window with all the DEAD BEEF numbers going onto the local stacks of each task as I scroll through a few task blocks. It's a useable thing now. \ mtask spawn tasks demo INCLUDE DSK1.TOOLS.F \ for debugging etc. INCLUDE DSK1.MTASK99.F INIT-MULTI \ create a task in heap, fork it, assign Execution token and run it : SPAWN ( xt -- ) USIZE MALLOC ( pid) DUP >R FORK R@ ASSIGN R> WAKE ; HEX 10 CONSTANT STKSIZE \ each task has ONLY 20 cells for each stack VARIABLE X \ used to test if tasks are running : DROPS ( n --) 0 DO DROP LOOP ; \ drop items from the stack : STKTHING \ fill and clear data stack so it can be seen in debugger BEGIN PAUSE STKSIZE 0 DO PAUSE DEAD LOOP STKSIZE DROPS PAUSE STKSIZE 0 DO PAUSE BEEF LOOP STKSIZE DROPS PAUSE 1 X +! AGAIN ; \ create and wake n tasks. VARIABLE #TASKS : TASKS ( n -- ) DUP #TASKS ! 0 DO ['] STKTHING SPAWN LOOP ; : KILLALL SINGLE USIZE #TASKS @ * MFREE INIT-MULTI ; 30TASKS.mp4 LOWMEM 30TASKS.MOV 5 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 20, 2018 Author Share Posted September 20, 2018 (edited) Everybody Wants to Write to the Screen One of the challenges in a multi-tasking system (or a multi-user system) is how to manage screen control. In a single task system you need a ROW variable and a COL variable to keep track of the cursor position. That works great until some other process on the machine tries to write to another location than your task is using. So how do we keep this straight between tasks? In Forth the answer is "USER variables" . USER variables are in a special memory block called the USER area. The USER area is unique for each task. In CAMEL99 Forth this includes the VROW and VCOL variables for VDP x,y control as well as the number BASE variable and a bunch of compiler control variables. So USER variables let each task have it's own copy of where it is using the VDP screen. That's good, but it does not solve the problem of converting digital numbers into text. Forth typically has a small buffer called the HOLD buffer where numbers are converted. It is placed just past the end of the dictionary typically, in unused memory. This means if you add new words to the dictionary the HOLD buffer moves up too. All good until two tasks try to convert a number to text at the same time! The solution in CAMEL99 Forth is to add a USER variable called TPAD (task's pad) that *sets the offset from the end of the dictionary where any tasks places it's hold buffer and also a general purpose buffer call PAD. (not to be confused with the TI-99 scratch-pad memory) *Credit where it's due: This idea comes from HsForth written by the late James Kalihan of Springboro Ohio So for each task we must set the offset a little bigger so each task has it's own little space. This could be automated, but on a little machine like the TI-99 it's best that you know where everything is. The demo code shows how that is done by creating 3 timers and "PULSE" monitor. Each timer writes to the screen but in different a radix. (base 10,16 and 2) The PULSE monitor shows how long it is taking for the multi-tasker to get around the entire set of tasks. Part of the reason it's slow is because the KSCAN code in ROM takes about 1.2 mS so that limits how fast we can get around the loop of tasks immediately. If we ran a program in the console things speed up nicely. Or writing a KSCAN that is properly cooperative would be the ideal solution. (remember this tasker is cooperative not preemptive so programs have to be friendly) The Demo video shows you how to control the tasks. Also notice that when we compile a source code file, the counters stop. This is by design because having multiple tasks compiling into the same dictionary would be fatal! Adding a SAMS memory page for each task would fix that but it's just an idea at the moment. \ 4tasks.f mtask demo with numeric printing INCLUDE DSK1.TOOLS.F INCLUDE DSK1.MTASK99.F INCLUDE DSK1.UDOTR.F \ Right justified number printing INIT-MULTI ( word to create a task in heap and FORK) : TASK: ( size -- ) MALLOC DUP FORK CONSTANT ; USIZE TASK: TASK1 USIZE TASK: TASK2 USIZE TASK: TASK3 USIZE TASK: TASK4 DECIMAL \ show the round-robin task time in uS. (TMR is 21.3 uS/tick) : .PULSE TMR@ PAUSE TMR@ - ABS 213 10 */ 6 U.R ." uS" ; \ single command to stop a task and goto next task : STOP ( -- ) MYSELF SLEEP PAUSE ; \ re-entrant up counter : UPCOUNTER ( col row end start -- ) ?DO 2DUP AT-XY I 8 U.R PAUSE LOOP 2DROP ; \ re-entrant down counter : DNCOUNTER ( col row end start -- ) SWAP ?DO 2DUP AT-XY I 8 U.R PAUSE -1 +LOOP 2DROP ; : COUNTER1 \ decimal counter DECIMAL 132 TPAD ! \ set task's PAD offset from HERE 0 0 1000 0 UPCOUNTER STOP ; : COUNTER2 \ hex counter 164 TPAD ! HEX 9 0 [ HEX ] 1FF 0 DNCOUNTER STOP ; DECIMAL : COUNTER3 \ perpetual binary counter 196 TPAD ! 2 BASE ! BEGIN 20 0 256 0 UPCOUNTER AGAIN ; : PULSER \ show round robin time 220 TPAD ! DECIMAL BEGIN 31 0 AT-XY PAUSE .PULSE 500 MS [ HEX ] 83D6 OFF \ kill screen time-out [ DECIMAL ] AGAIN ; ' COUNTER1 TASK1 ASSIGN ' COUNTER2 TASK2 ASSIGN ' COUNTER3 TASK3 ASSIGN ' PULSER TASK4 ASSIGN : RUN 0 C/L@ BL VFILL \ erase top line of screen C/L@ VTOP ! \ set top of screen to 2nd line PAGE CR ." MULTI-TASKING VDP I/O DEMONSTRATION" CR ." ---------------------------------------" CR ." 3 counter tasks in BASE 10,16 and 2" CR ." 1 pulse task, shows round robin" CR ." time in micro-seconds" CR CR ." Shows how tasks can write to screen" CR ." using standard screen words." CR CR ." Console is active." CR CR ." When counters stop use RESTART to" CR ." run them again. WAKE will crash!" MULTI TASK4 WAKE TASK1 WAKE TASK2 WAKE TASK3 WAKE ; SCREENIO.mp4 Edited September 20, 2018 by TheBF 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 22, 2018 Author Share Posted September 22, 2018 (edited) How to Do Conditional Compilation in a Tiny Machine I was quite happy with the progress on CAMEL99 Forth V2 once I got the file access working. The original vision for CAMEL99 was to make an 8K kernel that could extend itself with source code files. I thought is was quite cool to have the source code in text files rather than blocks So far so good but what about dependencies? What if you try to load a file that requires another file's words to be loaded first? Standard Forth has a set of words for this called [iF] [ELSE] [THEN]. You can use these words in your source code like this: ( Remember Forth's 'then' means "then go on with the rest of the program" :-) TRUE [IF] INCLUDE SOURCE1.FTH [ELSE] INCLUDE SOURCE2.FTH [THEN] This a very nice solution however to implement them takes quite a bit of code. Here are the official definitions : [ELSE] ( -- ) 1 BEGIN \ level BEGIN BL WORD COUNT DUP WHILE \ level adr len 2DUP S" [IF]" COMPARE 0= IF \ level adr len 2DROP 1+ \ level' ELSE \ level adr len 2DUP S" [ELSE]" COMPARE 0= IF \ level adr len 2DROP 1- DUP IF 1+ THEN \ level' ELSE \ level adr len S" [THEN]" COMPARE 0= IF \ level 1- \ level' THEN THEN THEN ?DUP 0= IF EXIT THEN \ level' REPEAT 2DROP \ level REFILL 0= UNTIL \ level DROP ; IMMEDIATE : [IF] ( flag -- ) 0= IF POSTPONE [ELSE] THEN ; IMMEDIATE : [THEN] ( -- ) ; IMMEDIATE This uses string comparisons so I would need to add COMPARE to the system, and it uses REFILL, which I have not implemented per the new Forth 2012 architecture. altogether it would consume over 100 244 bytes of precious TI-99 memory. So all that to say I am going to use two non-standard words called NEEDS and FROM. The entire code adds only 36 bytes to the system and it solves my problem nicely. In fact it adds a feature that [iF] [THEN] does not have. You specify the significant word needed with NEEDS. : NEED ( -- ?) BL WORD FIND NIP ; : FROM ( ? -- ) BL PARSE-WORD ROT ( addr len ? --) 0= IF INCLUDED ELSE 2DROP THEN ; With these two words loaded all you do is: NEEDS HCHAR FROM DSK1.GRAFIX.F NEEDS DUMP FROM DSK1.TOOLS.F NEEDS RND FROM DSK1.RANDOM.F The hard part now is going through the source files and putting the correct conditional information in them. :-) Edited September 22, 2018 by TheBF Quote Link to comment Share on other sites More sharing options...
Willsy Posted September 22, 2018 Share Posted September 22, 2018 That's really nice. You'll need a NEEDS ALL FROM ... too to make it really shine. Top job! Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 22, 2018 Author Share Posted September 22, 2018 That's really nice. You'll need a NEEDS ALL FROM ... too to make it really shine. Top job! Thanks. I wish it was that smart. :-) The NEEDS word is just checking the system for one word as a proxy for the file being loaded or not. I always liked the import directive from MODULA that let you do what you are referring to; pulling in specific words from a library. That's a bit bigger job for Forth, but it could be done. Probably makes more sense for a cross-compiler. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 24, 2018 Author Share Posted September 24, 2018 CAMEL99 Forth is Starting to Look like a Shell I have decided that adding the NEEDS/FROM utility is such a game changer that the system needs a revision number. So I will be releasing V2.1.0 this week with notes on the changes. One big change is the NEEDS/FROM words are loaded first in the START configuration file because it is so handy. To demonstrate what I mean I wrote a simple file copy utility. In the video you can see that when I load the COPY utility the ANSFILES code is pulled into the system, but when I load the MORE utility, to see file contents, it loads very quickly because the ANSFILES code is already compiled in the system and so only the MORE utility program is compiled. The file COPY code is starting to look very ANS/ISO Forth now. An interesting thing to note is how we parse the input file names with no temporary buffer. This is done by using "stack strings" that are just the address and length of a string residing on the data stack. The word FNAME cuts the text input, delimiting on a space character using PARSE-NAME. Each time it runs, it leaves the data for the string on the data stack. Even though both file name strings are in a single terminal input buffer, it is converted into two separate "stack strings" on the data stack. Neat! \ copy text file NEEDS OPEN-FILE FROM DSK1.ANSFILES.F NEEDS VALUE FROM DSK1.VALUES.F NEEDS PARSE-NAME FROM DSK1.PARSNAME.F HEX 0 VALUE #1 \ these hold the file handles 0 VALUE #2 : FNAME ( -- addr len ) PARSE-NAME DUP ?FILE ; : COPY ( <file1> <file2> ) FNAME FNAME ( -- addr len addr len ) DV80 W/O OPEN-FILE ?FILERR TO #2 DV80 R/O OPEN-FILE ?FILERR TO #1 55 MALLOC >R LINES OFF BEGIN #1 EOF 0= WHILE R@ 50 #1 READ-LINE ?FILERR DROP ( -- n) R@ SWAP #2 WRITE-LINE ?FILERR LINES 1+! REPEAT R> DROP \ DROP buffer address from rstack 55 MFREE \ release the buffer memory #2 CLOSE-FILE ?FILERR #1 CLOSE-FILE ?FILERR BASE @ >R DECIMAL CR ." Copy complete. " LINES @ . ." records" R> BASE ! ; FILECOPY.mp4 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 28, 2018 Author Share Posted September 28, 2018 The watchword of the day is read the manual carefully. I finally got CAMEL99 Forth transferred to floppy disk with the help of Karsul's SAMS card and Magic FM from Arcadeshopper. It loaded and ran great as long as I did not try to access a file. There is a little paragraph in the E/A Manual, on page 404, that states: >834A - >836D Used as a stack area by the interpreter, floating point routines, and DSR routines. Unless console routines are called by your assembly language program, this area is available for use. Notice the reference to DSR routines. I had protected 2 addresses in this area, but used the rest of the space. Well my clever idea of placing an array of user variables in the scratch-pad RAM was probably a bad idea. I suspect that is why my DSR routine works on Classic99 which does not emulate the DSR code, but fails on real iron. I am going to re-org the user variable list to avoid >834A..>836D and see what happens. The cross-compiler should make that not too painful. (brave words) :-) Quote Link to comment Share on other sites More sharing options...
+mizapf Posted September 28, 2018 Share Posted September 28, 2018 You should probably consider a test run on MAME when working with DSRs, since it uses the original ROMs. 1 Quote Link to comment Share on other sites More sharing options...
Tursi Posted September 28, 2018 Share Posted September 28, 2018 You should probably consider a test run on MAME when working with DSRs, since it uses the original ROMs. So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME. Configure a disk image drive on DSK1, DSK2, or DSK3 normally, then exit the emulator. Edit Classic99.ini and change the "Type" for that drive from '2' to '3', and it will access the image using the TI DSR. I don't recommend this for general use yet only because it doesn't support the usual range of Classic99 functions yet - it runs the DSR and nothing more. (So, 180k disk image maximum, DSK1-3 only). In additional when using the default DSR, Classic99 will emit debug in the logs if you stomp on the reserved DSR memory locations, telling you which locations you stomped on and what address the code that did the stomping was at. The debugger is there to help, particularly large amounts of debug are provided for disk access. The following tests are made for every disk access to the Classic99 DSR (and will debug a warning if violated): -verifies that the workspace is GPLWS at >83E0 -verifies that the CRU base is stored at >83D0 -verifies (loosely) that the DSR entry point was stored at >83D2 -validates the PAB address is greater than >000D (otherwise there isn't room for the PAB when you backup to the start) - this one will refuse to execute the DSR as well -validates that the DSR name length is 7 or less (this is the "DSK1" part and there are hard-coded limits) -validates that the PAB opcode is valid -checks several places if the PAB overruns the top of VRAM -warns on filenames (including path) longer than 32 characters -checks the top of VRAM pointer at >8370 for the disk buffers -verifies the first four bytes of the VRAM buffer header are intact (except LSB of top of RAM pointer) -verifies the number of files value is less than 9 (maximum supported by Classic99's DSR, more means corruption happened) - there is an option to breakpoint if these values are corrupt and a real TI controller card would crash on the call Then AFTER the DSR call happens, it again checks the pointer at >8370, and validates the first four bytes (including the VRAM top LSB skipped above - that's a bug that won't be an issue in Classic99 yet, I was envisioning CF7 support for that one...) It also checks to see if any user code touched any of the actual buffers. I don't know if that covers the case you ran into, I've been adding cases as I hit them myself. In general though, if Classic99 doesn't throw any warnings in the debug log, you have a pretty good chance of it working. If you test with the TI DSR option, then you're running the actual DSR code. That said, always good to test on multiple platforms, particularly if you are uncertain. The above options are meant to try and help you understand WHY it doesn't run. 4 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 28, 2018 Author Share Posted September 28, 2018 You should probably consider a test run on MAME when working with DSRs, since it uses the original ROMs. Thanks! 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 28, 2018 Author Share Posted September 28, 2018 So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME. Configure a disk image drive on DSK1, DSK2, or DSK3 normally, then exit the emulator. Edit Classic99.ini and change the "Type" for that drive from '2' to '3', and it will access the image using the TI DSR. I don't recommend this for general use yet only because it doesn't support the usual range of Classic99 functions yet - it runs the DSR and nothing more. (So, 180k disk image maximum, DSK1-3 only). In additional when using the default DSR, Classic99 will emit debug in the logs if you stomp on the reserved DSR memory locations, telling you which locations you stomped on and what address the code that did the stomping was at. The debugger is there to help, particularly large amounts of debug are provided for disk access. The following tests are made for every disk access to the Classic99 DSR (and will debug a warning if violated): -verifies that the workspace is GPLWS at >83E0 -verifies that the CRU base is stored at >83D0 -verifies (loosely) that the DSR entry point was stored at >83D2 -validates the PAB address is greater than >000D (otherwise there isn't room for the PAB when you backup to the start) - this one will refuse to execute the DSR as well -validates that the DSR name length is 7 or less (this is the "DSK1" part and there are hard-coded limits) -validates that the PAB opcode is valid -checks several places if the PAB overruns the top of VRAM -warns on filenames (including path) longer than 32 characters -checks the top of VRAM pointer at >8370 for the disk buffers -verifies the first four bytes of the VRAM buffer header are intact (except LSB of top of RAM pointer) -verifies the number of files value is less than 9 (maximum supported by Classic99's DSR, more means corruption happened) - there is an option to breakpoint if these values are corrupt and a real TI controller card would crash on the call Then AFTER the DSR call happens, it again checks the pointer at >8370, and validates the first four bytes (including the VRAM top LSB skipped above - that's a bug that won't be an issue in Classic99 yet, I was envisioning CF7 support for that one...) It also checks to see if any user code touched any of the actual buffers. I don't know if that covers the case you ran into, I've been adding cases as I hit them myself. In general though, if Classic99 doesn't throw any warnings in the debug log, you have a pretty good chance of it working. If you test with the TI DSR option, then you're running the actual DSR code. That said, always good to test on multiple platforms, particularly if you are uncertain. The above options are meant to try and help you understand WHY it doesn't run. As always this is super help. Thanks! I will make use of this. It's way simpler than loading an image on a floppy and debugging word by word in Forth, with limited tools... because I have no file support to load them. B Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 20, 2018 Author Share Posted October 20, 2018 CAMEL99 Forth is Alive on Real Iron It took waaaay longer than I expected, but I guess that's normal for S/W projects. Special thanks to all the 99ers on Atariage, you know who you are, who filled in my enormous knowledge gaps. I will be getting these updated files to GitHub over the weekend. CAMEL99 ON REALIRON.MOV 2 Quote Link to comment Share on other sites More sharing options...
RXB Posted October 20, 2018 Share Posted October 20, 2018 (edited) So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME. -warns on filenames (including path) longer than 32 characters Hmm the buffer path and size of EA (REA) and XB (RXB) is 39 characters including a length byte so 40 in total. Almost every single Cart including DM2 and all TI originals have a 40 character buffer which includes Length byte. I have no idea why you would limit it to 32? (Is this some arbitrary value you picked?) Edited October 20, 2018 by RXB Quote Link to comment Share on other sites More sharing options...
Tursi Posted October 20, 2018 Share Posted October 20, 2018 Hmm the buffer path and size of EA (REA) and XB (RXB) is 39 characters including a length byte so 40 in total. Almost every single Cart including DM2 and all TI originals have a 40 character buffer which includes Length byte. I have no idea why you would limit it to 32? (Is this some arbitrary value you picked?) It's not a limit, it's just a warning. (Also, this is JUST the filename, not including the device and options). While TI software was pretty good at having reasonable length, a lot of third party software is smaller, and indeed more than 10 characters is sometimes not guaranteed. As part of my long filename specification, I recommended 32 characters be consider a compromise (power of two), and just added it to the warnings. 1 Quote Link to comment Share on other sites More sharing options...
RXB Posted October 21, 2018 Share Posted October 21, 2018 LOL yea I put up to 255 support in RXB 1000 for path names, and 1 byte for length. Then discovered to my dismay 99% of the device DSR SUCK at only holding 39 and some as little as 10??? Which is as imaginative for future use as having 1 finger! Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 28, 2018 Author Share Posted October 28, 2018 (edited) I think I love CLASSIC99 In the course of writing documents for CAMEL99 Forth I needed a current version of a file in the TI system. With the FILECOPY utility in CAMEL Forth, I simply did this: COPY DSK1.START CLIP And then pasted the file into MS Word. Wow, how nice is that? File copy looks like this: NEEDS OPEN-FILE FROM DSK1.ANSFILES NEEDS VALUE FROM DSK1.VALUES NEEDS PARSE-NAME FROM DSK1.PARSNAME HEX 0 VALUE #1 \ these hold the file handles 0 VALUE #2 : FNAME ( -- addr len ) PARSE-NAME DUP ?FILE ; : COPY ( <file1> <file2> ) FNAME FNAME ( -- addr len addr len ) DV80 W/O OPEN-FILE ?FILERR TO #2 DV80 R/O OPEN-FILE ?FILERR TO #1 52 DUP MALLOC >R LINES OFF BEGIN R@ 50 #1 READ-LINE ?FILERR ( -- #bytes eof?) WHILE R@ SWAP #2 WRITE-LINE ?FILERR LINES 1+! REPEAT R> DROP \ DROP buffer address from rstack ( 52) MFREE \ release the buffer memory #2 CLOSE-FILE ?FILERR #1 CLOSE-FILE ?FILERR BASE @ >R DECIMAL CR ." Copy complete. " LINES @ . ." records" R> BASE ! ; Edited October 28, 2018 by TheBF 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 5, 2018 Author Share Posted November 5, 2018 (edited) Machine Forth "mini-compilers" speed up Forth code In the course of reviewing a TRIG lookup table I realized it would be cool if I used the 9900 "indexed" addressing mode to read numbers from the table. Normally this requires using the Assembler because the address of the table has to be hard coded into the instruction like this: TABLE BSS >1000 GETNUM MOV TABLE@(R4),R4 The Forth Assembler is actually a set of mini "assemblers" that each know how to assemble only 1 instruction. :-) Chuck Moore the inventor of Forth began using this concept to create "mini compilers" for a handful of instructions and he called this "machine Forth" Think along those lines I created two "mini-compilers" like this: \ machine Forth "compilers" \ R4 is CAMEL99 Forth cache register for Top of stack : 2*, ( n -- 2(n) A104 , ; \ A R4,R4 : +@, ( addr -- ) C124 , ( addr) , \ MOV addr@(R4),R4 The first one just multiplies R4 by two by compiling the A R4,R4 instruction into memory. The 2nd one adds an address to R4 and "fetches" the value at the computed address back into R4. In Forth that is a '+' and a '@' (Fetch) +@, "compiles" the instruction into memory and then it compiles the address into memory right after the instruction using the comma "number compiler". Now we can create a CODE word as simply as doing it in hi level Forth. DECIMAL CODE ]SIN ( ndx -- sin) 2*, SINTAB +@, NEXT, ENDCODE : ]SIN2 ( ndx -- sin) 2* SINTAB + @ ; \ FORTH equivalent By testing a run through the entire SIN table 100 times, we find the Machine Forth version is two times faster! DECIMAL : MFSIN 100 0 DO 91 0 DO I ]SIN DROP LOOP LOOP ; : FORTHSIN 100 0 DO 91 0 DO I ]SIN2 DROP LOOP LOOP ; Edited November 5, 2018 by TheBF 4 Quote Link to comment Share on other sites More sharing options...
Willsy Posted November 6, 2018 Share Posted November 6, 2018 SUPER COOL! 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 8, 2018 Author Share Posted November 8, 2018 (edited) So because I am not well behaved I avoided the issue of signed division in my Forth system, since my needs were always positive numbers. That is until I had to work with trigonometric coordinates... So Lee Stewart to the rescue! (again) I found the code in FBForth to be well suited to my needs so I re-coded it in structured assembler and added a variable called FLOOR to the system that let's one switch between symmetrical and floored division. Preliminary testing seems to show I got the logic correct but I will beat it up a bit more. Edits 1. Never trust preliminary testing. (it didn't work for all cases) 2. Working with your own tools is "interesting". CON: I found a bug in my cross-assembler with JLT instruction PRO: I fixed it! Hats off once again to Dr. Stewart. For the record here is where the code ended up. I then use M/MOD as a primitive that can perform symmetrical or floored division (default). M/MOD is a primitive that is used to derive the rest of the division family. ( */MOD, /MOD, / ) CODE: M/MOD ( lsb msb n3 -- rem quot) *SP+ R1 MOV, \ POP the high word of ud to r1 *SP R2 MOV, \ move low word of ud to r2 (keep stack pos.) TOS R3 MOV, \ DUP for sign of den R1 W MOV, \ DUP for sign of num R1 R5 MOV, \ DUP 2nd copy of num for symmetric sign TOS ABS, \ force den positive R1 0 CMPI, \ check sign of num LT IF, \ if numerator<0 R1 INV, \ DO DABS. invert num MSB and.. R2 NEG, \ ..negate num LSB OC IF, \ if carry=TRUE R1 INC, \ increment num MSB ENDIF, ENDIF, TOS R1 DIV, \ perform the division. R1=quot, R2=rem \ * Test for negative quotient R3 W XOR, \ compare signs of den and num LT IF, \ if different R1 NEG, \ negate quotient ENDIF, \ check for remainder R2 0 CMPI, NE IF, \ if <>0 R5 8000 ANDI, \ test for numerator negative NE IF, \ if signbit<>0 R2 NEG, \ rem. takes sign of num(symmetric) ENDIF, \ * Handle floored division, if necessary _floor @@ R0 MOV, \ symmetric or floored division? NE IF, \ if 0, it's symmetric and we're done W 8000 ANDI, \ use XOR result to check num and den signs NE IF, R1 DEC, \ signs different, so floor quot R3 R2 ADD, \ rem = den + rem ENDIF, ENDIF, ENDIF, R1 TOS MOV, \ quotient to tos R2 *SP MOV, \ put remainder on open stack location NEXT, \ we're outta here! END-CODE Edited November 9, 2018 by TheBF 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 14, 2018 Author Share Posted November 14, 2018 CAMEL99 Forth Stable Release V2.1.E https://github.com/bfox9900/CAMEL99-V2 I think I have this system in place where I feel pretty good about people really giving it a try. The new DSRLNK is stable and I have added floored division to this release as required by the ANS/ISO Forth standard. A variable in the system allows you to switch between Floored and Symmetrical division like this: FLOOR ON ( default) FLOOR OFF Here are the GitHub release notes. Beat it up! Nov 13, 2018 V2.1.E Floored division is now the default per ANS/ISO standard. Symmetrical division is set using the FLOOR variable: FLOOR OFF, FLOOR ON Due to the slow speed of the 9900 CPU floored division is coded in Forth Assembler. The code is a re-work of code, used by permission, from FB-Forth by Lee Stewart. ( http://fbforth.stewkitt.com/ ) Forth primitives are now separated into 2 files: 9900FAS2.HSF, TI99PRIMS.HSF RSTPAB (reset PAB) added to QUIT for stability when using file system. Improved ?TERMINAL so it waits for key release after key press. 2 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.