jschultzpedersen Posted April 29, 2023 Share Posted April 29, 2023 Hi The last few weeks I have looked at scrolling in TurboForth, and whether I could manage the real time update of 32 patterns (one complete line in Graphic mode 1) at a sensible speed only using standard Forth - no assembly routine calls. Perhaps a bit pointless, but bear with me! There is at least one mile-long discussion on scrolling using assembler or one of the Basic versions with specific support for it. But I did not see one involving TurboForth only. Anyway, I managed to do about 4.4 updates per second - that is 4.4 updates of 256 bytes of pattern data as well as counting in the effect of their neighbour pattern bytes and adding new data every 8 shifts. All calculations are done on a copy of the pattern data in RAM, and the updated data are then block moved to VRAM. This produces a stutter-free update of the screen and increases speed because it does not have to read/write data in VRAM all the time. The central SCROLL word is surprisingly simple... CSTART is the address in RAM for the copy of the pattern data. PIXLEN is the total length of the pattern bytes. A call to SCROLL will shift everything 1 pixel to the left. : SCROLL CSTART PIXLEN + CSTART DO I DUP DUP C@ 1 << SWAP 8 + C@ 7 >> + SWAP C! LOOP ; After 8 repeats I call a word that updates the rightmost pattern to the pattern of the next character. This pattern is not displayed on screen, and it scrolls nicely on screen during the next 8 shifts. 4.4 updates per second is still kind of slow, but if you only want to display 16 patterns, the speed roughly doubles, and if you modify it to do 2-pixels scroll (simply change the shift values), the speed redoubles again, and then we have something. Besides, it is very good at making scrolling starfields with large numbers of stars by repeating sets of patterns at different lines on the screen and making sure they start at different x-locations. You can also feed text strings of any length to it and repeat them endlessly just by resetting a pointer. So it is not without practical use. This note is just to let others know I am having a lot of fun learning TurboForth 😀 regards Jesper 4 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 29, 2023 Share Posted April 29, 2023 Though it is whole-character and not pixel-level scrolling, are you aware of TurboForth’s built-in SCROLL word? It will scroll a pre-defined panel in any direction, with or without wrapping. ...lee Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted April 30, 2023 Author Share Posted April 30, 2023 Hi Yes, I looked at SCROLL. But as you say it has a resolution of 1 character whereas I want pixel-resolution movement. SCROLL is fine for moving a larger panel, but it looks chunky if I want to move waves or stars or similar objects that repeats themselves horizontally. My routine is also ok for situations where you need to animate background tiles located in multiple positions on the screen. A 2 pattern sequence with 1 pixel resolution could be drawn at around 50 frames per second with time to spare. By the way. I looked at a discussion about hooking code to an interrupt. The examples all dealt with assembler code sequences. Is it possible to hook a Forth word directly to an interrupt by pointing to the words address? I seem to remember that the Basic provided with the MSX computers had an EVENT command or something like that, that could hook a basic subroutine to an interrupt. Of course, Basic being Basic, it only allowed you to use very small sub routines. But TurboForth is so much faster. regards Jesper 2 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 30, 2023 Share Posted April 30, 2023 17 hours ago, jschultzpedersen said: By the way. I looked at a discussion about hooking code to an interrupt. The examples all dealt with assembler code sequences. Is it possible to hook a Forth word directly to an interrupt by pointing to the words address? I seem to remember that the Basic provided with the MSX computers had an EVENT command or something like that, that could hook a basic subroutine to an interrupt. Of course, Basic being Basic, it only allowed you to use very small sub routines. But TurboForth is so much faster. Simply putting the execution address of a Forth word at >83C4 (the ISR hook) will not work because it would interfere with the inner interpreter. Without setting up your own TurboForth ISR in Assembly Language Code (ALC) to mesh with the inner interpreter, you would need to set up an ALC routine with the following scenario: Place ALC entry point at >83C4. Current workspace (WS) is >83E0 (GPLWS). Change to your WS if needed. Do your interrupt stuff. Make >83E0 the current WS. RT (to console ISR). Because the last thing the console ISR does is to change to the ISRWS (>83C0) and RTWP to calling process, you can change (5) and (6) to 5. Make >83C0 the current WS. 6. RTWP (to process that called the console ISR). If you want to look at how to implement a Forth ISR, take a look at how TI Forth did it in Chapter 10 of the TI Forth Manual and the code in the spoiler: Spoiler * *** INTERRUPT SERVICE * INT1 LI TEMP1,INT2 FIX 'NEXT' SO THAT INTERRUPT IS MOV TEMP1,@2*NEXT+MAINWS PROCESSED AT END OF LWPI >83C0 NEXT 'CODE' WORD RTWP * INT2 LIMI 0 MOVB @>83D4,TEMP0 SRL TEMP0,8 ORI TEMP0,>100 ANDI TEMP0,>FFDF BLWP @VWTR TURN OFF VDP INTERRUPTS LI NEXT,$NEXT RESTORE 'NEXT' SETO @INTACT DECT R SET UP RETURN LINKAGE MOV IP,*R LI IP,INT3 MOV @$ISR(U),W DO THE FORTH ROUTINE B @DOEXEC INT3 DATA $+2 DATA $+2 MOV *R+,IP CLR @INTACT MOVB @>83D4,TEMP0 SRL TEMP0,8 AI TEMP0,>100 MOVB @VDPSTA,TEMP1 REMOVE PENDING INTERRUPT BLWP @VWTR LIMI 2 B *NEXT CONTINUE NORMAL TASK *=========================================================== BKLINK MOV @INTACT,TEMP7 JNE BKLIN1 LIMI 2 BKLIN1 B *LINK Here is how I did it in fbForth—see Chapter 10 of fbForth 2.0: A File-Based Cartridge Implementation of TI Forth and my augmentation of TI Forth’s code (basically the same as TI Forth but with Forth sound and speech processing and better comments IMHO) in the spoiler: Spoiler * _____ ____ __ __ ___________ * / _/ / / __/__ ____/ /_/ / / _/ __/ _ \ * / _/ _ \/ _// _ \/ __/ __/ _ \ _/ /_\ \/ , _/ * /_//_.__/_/ \___/_/ \__/_//_/ /___/___/_/|_| * ;[*** Interrupt Service ======================================================= * This routine is executed for every interrupt. It processes any pending * speech and sound. It then looks to see whether a user ISR is installed in * ISR. If so, it sets up NEXT for execution of the user ISR. This will work * only if the user has installed an ISR using the following steps in the fol- * lowing order: * * (1) Write an ISR with entry point, say MYISR. * (2) Determine code field address of MYISR with this high-level Forth: * ' MYISR CFA * <<< Maybe need a word to do #3 >>> * (3) Write CFA of MYISR into user variable ISR. * * Steps (2)-(3) in high-level Forth are shown below: * ' MYISR CFA * ISR ! * * <<< Perhaps last step above should be by a word that disables interrupts >>> * * The console ISR branches to the contents of >83C4 because it is non-zero, * with the address, INT1, of the fbForth ISR entry point below (also, the * contents of INTLNK). This means that the console ISR will branch to INT1 * with BL *R12 from WP = GPLWS (>83E0), R12 containing INT1 below to first * process any pending speech and sound. * * If the user's ISR is properly installed, the code that processes the user * ISR modifies NEXT so that the very next time B *NEXT or B *R15 is executed * from Forth's workspace (MAINWS), the code at INT2 will process the user's * ISR just before branching to the normal NEXT entry ($NEXT) in fbForth's * inner interpreter. *** ========================================================================== * ¡¡¡ MUST REMEMBER THAT WE ARE IN GPL WORKSPACE UPON ENTRY. !!! INT1 LI R0,BRSTK load address of top of Branch Address Stack * * Set up for pending speech * MOV @SPCSVC,*R0 save Speech service address onto Branch Stack JEQ SNDCH1 jump to sound-check if no speech INCT R0 increment Branch Stack * * Set up for pending sound table #1 (ST#1) * SNDCH1 MOV @SND1ST,R2 sound table ST#1 to service? JEQ SNDCH2 process speech and sound if needed LI R1,PLAYT1 load PLAYT1 address and... MOV R1,*R0+ ...push it onto Branch Stack * * Set up for pending sound table #2 (ST#2) * SNDCH2 MOV @SND2ST,R3 sound table ST#2 to service? JEQ PRCSPS process speech and sound if needed LI R1,PLAYT2 load PLAYT2 address and... MOV R1,*R0+ ...push it onto Branch Stack * * Process sound stack if both sound tables idle * PRCSPS SOC R2,R3 OR R2 and R3..both sound tables idle? JNE PRSPS2 nope..skip sound stack processing LWPI SND1WS switch to ST#1 WS CI R4,SNDST0 anything on sound stack? JEQ PRSPS1 no..exit sound stack processing DECT R4 pop sound stack position MOV *R4,R2 get sound table address from sound stack INC R0 kick off sound processing of ST#1 (R0=1) PRSPS1 LWPI GPLWS switch back to GPL WS * * Check for any pending speech and sound * PRSPS2 CI R0,BRSTK any speech or sound to process? JEQ USRISR if not, jump to user ISR processing LI R1,BNKRST yup..load return address MOV R1,*R0 push return address onto Branch Stack * * Process pending speech and sound * MOV @MYBANK,@BANKSV save bank at interrupt CLR @>6002 switch to bank 2 for speech & sound services LI R7,BRSTK load top of Branch Stack MOV *R7+,R8 pop speech/sound ISR B *R8 service speech/sound * * Restore interrupted bank * BNKRST ; return point for speech and sound ISRs MOV @BANKSV,R0 restore bank at interrupt SRL R0,13 get the bank# to correct position AI R0,>6000 make it a real bank-switch address CLR *R0 switch to the bank at interrupt * * Process User ISR if defined * USRISR MOV @$ISR+$UVAR,R0 User ISR installed? JEQ INTEX * * Fix NEXT so that the user's ISR is processed the next time B *NEXT (B *R15) * is executed from Forth's WS (MAINWS = >8300), which it does at the end of * every CODE word, keyboard scan and one or two other places. * LI R1,INT2 Load entry point, INT2 MOV R1,@2*NEXT+MAINWS Copy it to Forth's NEXT (R15) * * The following 2 instructions are copies of the remainder of the console ROM's * ISR (except that 'CLR R8' was removed because it is only needed by TI Basic) * because we're not going back there! * INTEX LWPI >83C0 Change to console's ISR WS RTWP Return to caller of console ISR * * Branch through above-modified NEXT (R15) gets us here. NEXT will be restored * before executing user's ISR. INT3 (cleanup routine below) will be inserted * in address list to get us back here for cleanup after user's ISR has finished. * User's ISR is executed at the end of this section just before INT3. * INT2 LIMI 0 Disable interrupts LI R0,>0001 Set up for VR01 (LSB to MSB after SWPB below) MOVB @>83D4,R0 Get copy of VR01 to MSB (>nn01) SWPB R0 now ready for register (>01nn) ANDI R0,>FFDF Clear VDP-interrupt-enable bit BLWP @VWTR Turn off VDP interrupt LI NEXT,$NEXT Restore NEXT SETO @INTACT Set Forth "pending interrupt" flag DECT R Set up return linkage by pushing MOV IP,*R ...IP (R13, next Forth CFA) to return stack and LI IP,INT3 ...setting IP to INT3 (below) for cleanup MOV @$ISR(U),W Do the user's Forth ISR by executing B @DOEXEC ...it through Forth's inner interpreter * * Clean up and re-enable interrupts. * INT3 DATA INT3+2 $NEXT (or $SEMIS) puts INT3+2 in W (R10) DATA INT3+4 DOEXEC (or $SEMIS) will branch to *W = INT3+4 (next instr) MOV *R+,IP Start cleanup: pop IP from before call to user's ISR CLR @INTACT Clear Forth "pending interrupt" flag LI R0,>0001 Set up to restore VR01 (LSB to MSB after SWPB below) MOVB @>83D4,R0 Get copy of VR01, with VDP int enabled, to MSB (>nn01) SWPB R0 now ready for VR01 (>01nn) to re-enable VDP int MOVB @VDPSTA,R1 Remove pending VDP interrupt by reading VDP status BLWP @VWTR Write VR01 LIMI 2 Re-enable interrupts B *NEXT Continue normal task ;]* Another thing to keep in mind is that TurboForth operates with interrupts normally disabled whereas TI Forth and fbForth operate the other way round. ...lee 3 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted April 30, 2023 Author Share Posted April 30, 2023 Hi Ahh - It looks like there is another area to dig into... Thanks for the tips! regards Jesper Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 2, 2023 Share Posted May 2, 2023 I wish I could have made the ISR capable of supporting a Forth word. At the time I couldn't work out how to do it. I wonder if it's possible to write an assembly language ISR for TF that sets up a Forth environment to go and execute the word? It's probably possible since the Forth environment are all stored in registers: R3=Forth PC, R4=stack pointer, R5=return stack pointer and R12 points to NEXT. The other thing to bear in mind is that TF only enables interrupts at certain times. Basically, whenever you access VDP memory in some way. It will enable and then disable interrupts. For things like games, where you're always writing to VDP in some way it doesn't seem to pose a problem. It would be interesting if you re-wrote your scrolling logic in assembly and linked it to the ISR. Then run a tight loop like this: : tight ( --) begin 0 v@ drop again ; It should run really fast. One way to do that is to use the TF assembler. Then tick the word, add 2 to the returned address, and that's the address you install into the ISR vector. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 2, 2023 Share Posted May 2, 2023 4 hours ago, Willsy said: I wonder if it's possible to write an assembly language ISR for TF that sets up a Forth environment to go and execute the word? I did it in TI-Forth back in 1983 or so. It's in the the TI-Forth docs how to do it. The thing you have to watch out for is the size of the routine. If we say that the average Forth word runs in 100uS (code+NEXT) and the interrupt runs every 16mS (16000uS) then 160 Forth words will consume the entire CPU. So by that measure 40 words eats 25% of the CPU time etc. After I experimented with it I never found a suitable use. I think a CODE word is the better alternative Something to remember, the CODE word must end with RT, not NEXT, This code looks like it would work on Turbo Forth with some simple changes. https://github.com/bfox9900/CAMEL99-ITC/blob/master/LIB.ITC/ISRSUPPORT.FTH 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 2, 2023 Share Posted May 2, 2023 6 hours ago, Willsy said: It would be interesting if you re-wrote your scrolling logic in assembly and linked it to the ISR. Then run a tight loop like this: : tight ( --) begin 0 v@ drop again ; It should run really fast. This will not work if any of the scrolling routine ISR enables interrupts, such as use of any TF VDP access words will do. A user ISR that allows interrupts will be aborted and re-started if an interrupt occurs while in the the user ISR. 6 hours ago, Willsy said: One way to do that is to use the TF assembler. Then tick the word, add 2 to the returned address, and that's the address you install into the ISR vector. This will work as long as Interrupts are not enabled within the user ISR by you or a called function; You Realize the GPL workspace (WS) is current, Change to TurboForth’s WS ( $8300 LWPI, ), or Change to your WS (slowest, if not in scratchpad RAM) End with RT, (realizing R8 of current WS will be cleared by console ISR) or $83C0 LWPI, (console ISR’s WS) followed by RTWP, (returns to interrupted routine, neither clearing current R8 nor returning to console ISR) ...lee 2 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 3, 2023 Author Share Posted May 3, 2023 Hi The thought that assembler code would be the next step has crossed my mind. I have looked at the TF assembler in the last few days. I only have limited experience using TMS assembler, so there is some catching up to do, but I am working on it. By the way, I am confused by some of the examples in the manual v. 1.1. In the example in section 4 it says.... ASM: FRAME SP INCT (This works if I write DECT,) Similarly in the example 8.1 we have... ASM: ADD *SP R0 MOV, SP DECT, (This works if I write INCT,) And in the string example 9.4, where Indexed memory addressing is used I have to use offset 2 instead of -2 to get the string address. Has there been a change in the direction the stack grows since the manual was written? regards Jesper Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 3, 2023 Share Posted May 3, 2023 1 hour ago, jschultzpedersen said: Hi The thought that assembler code would be the next step has crossed my mind. I have looked at the TF assembler in the last few days. I only have limited experience using TMS assembler, so there is some catching up to do, but I am working on it. By the way, I am confused by some of the examples in the manual v. 1.1. In the example in section 4 it says.... ASM: FRAME SP INCT (This works if I write DECT,) Similarly in the example 8.1 we have... ASM: ADD *SP R0 MOV, SP DECT, (This works if I write INCT,) And in the string example 9.4, where Indexed memory addressing is used I have to use offset 2 instead of -2 to get the string address. Has there been a change in the direction the stack grows since the manual was written? regards Jesper Yes. @Willsy will surely be along to give you more information. You should reference the current manual, which addresses both versions of TurboForth. Examples, like those you cited, are updated. FYI, the 8.1 ADD example can be made even more efficient with ASM: ADD ( n1 n2 — n1+n2) *SP+ *SP A, ;ASM ...lee 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 3, 2023 Share Posted May 3, 2023 4 hours ago, jschultzpedersen said: Hi The thought that assembler code would be the next step has crossed my mind. I have looked at the TF assembler in the last few days. I only have limited experience using TMS assembler, so there is some catching up to do, but I am working on it. By the way, I am confused by some of the examples in the manual v. 1.1. In the example in section 4 it says.... ASM: FRAME SP INCT (This works if I write DECT,) Similarly in the example 8.1 we have... ASM: ADD *SP R0 MOV, SP DECT, (This works if I write INCT,) And in the string example 9.4, where Indexed memory addressing is used I have to use offset 2 instead of -2 to get the string address. Has there been a change in the direction the stack grows since the manual was written? regards Jesper Yes - exactly that!! The stacks go the other way now! I'll have to update the page. 3 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 3, 2023 Author Share Posted May 3, 2023 Hi Ahh. I am from the age of books, and I like to position myself in a comfy chair and read stuff from paper versions. So I just downloaded the PDF version. 😃 Problem solved! regards Jesper 3 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 26, 2023 Author Share Posted May 26, 2023 Hi again I have progressed in my scrolling project and have now managed to create an embedded TF assembly code version for my testing, that allows for about 40 updates per second, which is of course far better than the 4.5 updates in the original TurboForth version. The routine relies heavily on using registers for speed reasons. It occurred to me, that running the routine in 16 bit RAM would benefit me further, getting rid of wait states and all that. But I have a problem with creating a local workspace anywhere in RAM. The manual (The internet version chapter 9.2) has an example with multiplication and a local workspace. But if I run it (copied directly from the manual), it locks up. Apart from the tested example I have tried to store and restore the original workspace around the code as well. But no luck there. Please - Where do I go wrong? regards from a bleary-eyed Jesper 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 26, 2023 Share Posted May 26, 2023 Hi Jesper, The main problem is the demo code (in the second example of chapter 9.2) is not restoring TurboForth's workspace. In fact, the first example in that chapter also exhibits the same problem, but isn't meant to actually be executed. It is just for illustration. I've corrected the code on the WEB version, the PDF will take longer - assuming I still have the original Word source file! Here's the corrected second example from chapter 9.2. If you spot any more errors please let me know. I'll get around to fixing them eventually! Mark CREATE WORKSPACE \ create dictionary entry for our workspace 16 CELLS ALLOT \ reserve space for 16 registers ASM: MULTIPLY WORKSPACE LWPI, \ use our workspace R1 R8 MPY, \ do the multiply $8300 LWPI, \ restore TurboForths workspace ;ASM ASM: DO-WORK WORKSPACE LWPI, \ use our workspace R1 200 LI, \ load R1 R8 4 LI, \ load R8 $8300 LWPI, \ restore TurboForths workspace ;ASM : TEST-ASM DO-WORK MULTIPLY \ "chain" DO-WORK and MULTIPLY WORKSPACE R9 2* + @ . \ display the result from register 9 ; 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 26, 2023 Share Posted May 26, 2023 Regarding using your own registers in PAD ram, you might struggle. As the PAD RAM Memory Map shows, PAD ram is pretty full. It has a lot of the oft used Forth words in there for speed purposes. You might be able to use the area at >83C0 (not sure if TI ROM or something uses it). Failing that you could probably use TurboForth register space, just save R3, R4, and R5, and restore them afterwards. They are probably the only three that matter. Mark 1 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 26, 2023 Share Posted May 26, 2023 The area at >83c0 seems relatively safe. From what I can see, a few of the memory locations are used during file access and keyboard access. There's a detailed breakdown of the TI ROM/Interpreter/GPL use here: http://www.unige.ch/medecine/nouspikel/ti99/padram.htm 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 26, 2023 Share Posted May 26, 2023 3 hours ago, Willsy said: You might be able to use the area at >83C0 (not sure if TI ROM or something uses it). >83C0 is the Interrupt Service Routine’s (ISR’s) workspace and is also used by KSCAN and other routines: ...lee 2 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 27, 2023 Author Share Posted May 27, 2023 Hi again Just a quick feed back... The area from >83C0 causes problems. While the my routine will run, unpleasant things happen afterwards, when I tap a key. Not surprising considering some of the comments provided by Lee Stewart. I had better luck with >8300. I have avoided using R3, R4, R5, R6 or R12 in my code, and thus it seems to work even without restoring any registers. On the downside, the effect of explicitly using PAD ram is not apparent. But then, is the workspace used by TF not already in the PAD area? If the assembler code uses the same workspace I am already using 16 bit RAM. It seems to be the case, since the STWP instruction returns address >8300. For anyone interested in the MC code used to scroll a set of patterns stored in CPU RAM. Here is the code. CEND is a VARIABLE used to store the address of the last byte in the last pattern of the sequence. CWID is a CONSTANT holding the number of patterns in the sequence. Block 101 0.ASM: SCROLL ( -- ) . 1. CEND @@ R0 MOV, R13 8 LI, R15 4 LI, . 2. BEGIN, . 3. R14 CWID LI, R0 R1 MOV, R11 CLR, R10 CLR, . 4. BEGIN, . 5. R1 ** R7 MOV, R7 9 SLA, . 6. OC IF, R10 R7 A, R10 $100 LI, . 7. ELSE, R10 R7 A, R10 0 LI, ENDIF, R7 SWPB, . 8. R1 ** R8 MOV, R8 $FF00 ANDI, R8 1 SLA, . 9. OC IF, R11 R8 A, R11 $100 LI, . 10. ELSE, R11 R8 A, R11 0 LI, ENDIF, . 11. R7 R8 A, R8 R1 ** MOV, R13 R1 S, R14 DEC, . 12. EQ UNTIL, . 13. R0 DECT, R15 DEC, . 14. EQ UNTIL, . 15.;ASM --> . The TF code wrapped around the SCROLL routine currently looks like this... First the initialization stuff... Block 100 Mode:OVER 0.VARIABLE CSTART VARIABLE CEND VARIABLE TADRS VARIABLE TLEN . 1.33 CONSTANT CWID CWID 8 * CONSTANT PIXLEN 160 CONSTANT CH . 2.CH 8 * $800 + CONSTANT PSTART 0 CONSTANT X 21 CONSTANT Y . 3.32 CONSTANT WID Y WID * X + CONSTANT SSTART . 4.0 VALUE TPTR . 5.: SETUP ( -- ) . 6. HERE CSTART ! ALLOT PIXLEN CSTART @ PIXLEN + 2 - CEND ! . 7. CSTART @ PIXLEN + CSTART @ DO 0 I C! LOOP . 8. PSTART CSTART @ PIXLEN VMBW . 9. 32 0 DO CH I + SSTART I + V! LOOP ; . 10.: TSETUP ( ADRS LEN -- ) . 11. TLEN ! HERE TADRS ! TLEN @ ALLOT TADRS @ TLEN @ CMOVE ; . 12.: FEEDCHAR ( -- ) . 13. TADRS @ TPTR + C@ 8 * $800 + . 14. 8 0 DO DUP I + V@ CSTART @ PIXLEN + 8 - I + C! LOOP 1 +TO TPTR. 15. DROP ; --> . Then the main program...(DEMO) Block 102 Mode:OVER 0.. SHOWALL ( -- ) . 1. PSTART CSTART @ PIXLEN VMBW ; . 2.: DEMO ( -- ) . 3. 1 GMODE . 4. S" THIS IS A TALE OF MYSTERY AND SUSPENSE !" TSETUP . 5. SETUP . 6. TLEN @ 0 DO . 7. FEEDCHAR . 8. 8 0 DO . 9. SCROLL . 10. SHOWALL . 11. LOOP . 12. LOOP . 13. S" THE END..." TYPE CR CR . 14.; . 15. . The text scrolls 33 character patterns at about 5 characters per second or about 40 pixels per second. For every 8 pixels, a character pattern for the next character to be displayed is extracted from VDP RAM and inserted in the non-visible pattern of the last pattern in the sequence. I have used the patterns at 160-192. regards Jesper 2 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 27, 2023 Share Posted May 27, 2023 I've still got the word FILE btw 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 27, 2023 Share Posted May 27, 2023 2 hours ago, jschultzpedersen said: Hi again Just a quick feed back... The area from >83C0 causes problems. While the my routine will run, unpleasant things happen afterwards, when I tap a key. Not surprising considering some of the comments provided by Lee Stewart. I had better luck with >8300. I have avoided using R3, R4, R5, R6 or R12 in my code, and thus it seems to work even without restoring any registers. On the downside, the effect of explicitly using PAD ram is not apparent. But then, is the workspace used by TF not already in the PAD area? If the assembler code uses the same workspace I am already using 16 bit RAM. It seems to be the case, since the STWP instruction returns address >8300. For anyone interested in the MC code used to scroll a set of patterns stored in CPU RAM. Here is the code. CEND is a VARIABLE used to store the address of the last byte in the last pattern of the sequence. CWID is a CONSTANT holding the number of patterns in the sequence. Block 101 0.ASM: SCROLL ( -- ) . 1. CEND @@ R0 MOV, R13 8 LI, R15 4 LI, . 2. BEGIN, . 3. R14 CWID LI, R0 R1 MOV, R11 CLR, R10 CLR, . 4. BEGIN, . 5. R1 ** R7 MOV, R7 9 SLA, . 6. OC IF, R10 R7 A, R10 $100 LI, . 7. ELSE, R10 R7 A, R10 0 LI, ENDIF, R7 SWPB, . 8. R1 ** R8 MOV, R8 $FF00 ANDI, R8 1 SLA, . 9. OC IF, R11 R8 A, R11 $100 LI, . 10. ELSE, R11 R8 A, R11 0 LI, ENDIF, . 11. R7 R8 A, R8 R1 ** MOV, R13 R1 S, R14 DEC, . 12. EQ UNTIL, . 13. R0 DECT, R15 DEC, . 14. EQ UNTIL, . 15.;ASM --> . The TF code wrapped around the SCROLL routine currently looks like this... First the initialization stuff... Block 100 Mode:OVER 0.VARIABLE CSTART VARIABLE CEND VARIABLE TADRS VARIABLE TLEN . 1.33 CONSTANT CWID CWID 8 * CONSTANT PIXLEN 160 CONSTANT CH . 2.CH 8 * $800 + CONSTANT PSTART 0 CONSTANT X 21 CONSTANT Y . 3.32 CONSTANT WID Y WID * X + CONSTANT SSTART . 4.0 VALUE TPTR . 5.: SETUP ( -- ) . 6. HERE CSTART ! ALLOT PIXLEN CSTART @ PIXLEN + 2 - CEND ! . 7. CSTART @ PIXLEN + CSTART @ DO 0 I C! LOOP . 8. PSTART CSTART @ PIXLEN VMBW . 9. 32 0 DO CH I + SSTART I + V! LOOP ; . 10.: TSETUP ( ADRS LEN -- ) . 11. TLEN ! HERE TADRS ! TLEN @ ALLOT TADRS @ TLEN @ CMOVE ; . 12.: FEEDCHAR ( -- ) . 13. TADRS @ TPTR + C@ 8 * $800 + . 14. 8 0 DO DUP I + V@ CSTART @ PIXLEN + 8 - I + C! LOOP 1 +TO TPTR. 15. DROP ; --> . Then the main program...(DEMO) Block 102 Mode:OVER 0.. SHOWALL ( -- ) . 1. PSTART CSTART @ PIXLEN VMBW ; . 2.: DEMO ( -- ) . 3. 1 GMODE . 4. S" THIS IS A TALE OF MYSTERY AND SUSPENSE !" TSETUP . 5. SETUP . 6. TLEN @ 0 DO . 7. FEEDCHAR . 8. 8 0 DO . 9. SCROLL . 10. SHOWALL . 11. LOOP . 12. LOOP . 13. S" THE END..." TYPE CR CR . 14.; . 15. . The text scrolls 33 character patterns at about 5 characters per second or about 40 pixels per second. For every 8 pixels, a character pattern for the next character to be displayed is extracted from VDP RAM and inserted in the non-visible pattern of the last pattern in the sequence. I have used the patterns at 160-192. regards Jesper You are having way too much fun. Nicely done. Yes TF workspace is >8300. Something that is very common on intel CPUs and others is to push registers onto a stack to free them up and then restore them when you are finished. People tend not to do this on the 9900 CPU but it is pretty simple in a Forth configuration. You can even use the colon compiler to make Assembler macros for yourself. For example: : RPUSH, ( register --) RP INCT, *RP MOV, ; : RPOP, ( register --) *RP+ SWAP MOV, ; With these you can save critical TF registers before starting your code: ( Check the docs for the which ones are used by TF) ASM: MYCOOLCODE R5 RPUSH, R6 RPUSH, R7 RPUSH, R8 RPUSH, <COOL STUFF> R8 RPOP, R7 RPOP, R6 RPOP, R5 RPOP, \ restore in reverse order ;ASM There is of course overhead incurred, but TF has a good number of free registers so most of the time you might only need to push/pop a couple of extras. And of course you could use the data stack for parameters and temporary storage for your assembler programs too. Indexed addressing could be another way to use un-allocated stack space. Should be slightly faster for saving, a bit slower for restoring. ( I have never tried this one. This could be done with the data stack or the return stack. ) Of course you must keep the stack register in the workspace. \ save R0 -2 SP () MOV, R1 -4 SP () MOV, R2 -6 SP () MOV, ... \ restore -2 SP () R0 MOV, -4 SP () R1 MOV, -6 SP () R2 MOV, 3 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 27, 2023 Author Share Posted May 27, 2023 Aehmmm... A slight mistake in the code. in block 100 line 6 it says ALLOT PIXLEN. It should be PIXLEN ALLOT. Oh dear! regards Jesper 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 28, 2023 Share Posted May 28, 2023 6 hours ago, jschultzpedersen said: Aehmmm... A slight mistake in the code. in block 100 line 6 it says ALLOT PIXLEN. It should be PIXLEN ALLOT. Oh dear! regards Jesper Well I certainly hope it doesn't happen again. Forth's saving grace on that kind of thing is interactively testing small pieces of code at console and examining the results. I can't live without that feature because I am a bug generator "extrordinaire". I thought I would share with you some conventions that have evolved over the last 50 years in the Forth world. They may be useful to you ... or not. \ original CSTART @ PIXLEN + CSTART @ DO 0 I C! LOOP \ alternative \ used frequently for setting up do/loops over memory : BOUNDS OVER + SWAP ; CSTART @ PIXLEN BOUNDS DO 0 I C! LOOP Handy string words (These create byte counted strings which may not be long enough for every use but most of the time 255 bytes is plenty) \ place a string at the destination address as byte counted string : PLACE ( src n dst -- ) 2DUP C! 1+ SWAP CMOVE ; \ compile a string into dicionary space as BYTE counted string : S, ( caddr u -- ) HERE OVER 1+ ALLOT PLACE ALIGN ; : COUNT ( caddr -- addr len) \ In TF kernel. Convert a counted string address to a add,len pair Usage example CREATE MYSTRING S" Now is the time for all good men ..." S, MYSTRING COUNT TYPE or CREATE can make some string variables of fixed size CREATE A$ 80 ALLOT \ or if you making a lot of these BUFFER: is an ANS standard word : BUFFER: CREATE ALLOT ; 80 BUFFER: B$ : PRINT ( caddr --) COUNT CR TYPE ; \ sugar S" Please store this somewhere" A$ PLACE \ copy by converting to a addr,len and using PLACE A$ COUNT B$ PLACE A$ PRINT B$ PRINT These were tested on TurboForth. Maybe they are useful. 3 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 28, 2023 Author Share Posted May 28, 2023 Hi Conventions learned are always welcome, as they are so useful. Thanks. I guess that is why they became conventions 😀 As for very long strings, I was intending to feed them from data stored in a block and loaded into a section of memory. That gives me up to 1 Kb of text that is easily replaced. I tested for the effect of using 8 or 16 bit memory with the SCROLL word. It is quite substantial. Having the workspace in 16 bit PAD is about 30 % faster. All those wait states add up. regards Jesper 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 28, 2023 Share Posted May 28, 2023 5 hours ago, jschultzpedersen said: Hi Conventions learned are always welcome, as they are so useful. Thanks. I guess that is why they became conventions 😀 As for very long strings, I was intending to feed them from data stored in a block and loaded into a section of memory. That gives me up to 1 Kb of text that is easily replaced. I tested for the effect of using 8 or 16 bit memory with the SCROLL word. It is quite substantial. Having the workspace in 16 bit PAD is about 30 % faster. All those wait states add up. regards Jesper Super. If I think of/or see any other opportunities in your code I will give you what I have seen in those matters. Chuck Moore, of Forth fame, has said that the first time he saw other people's Forth code he was surprised how differently they used it than he did. He also confessed that he found it hard to explain how he uses the language to other people. My only recourse was to look at other people's code. Yes. It is such a shame that TI didn't give us 1K bytes of that 16 bit memory. Some the guys around here have modified their consoles with modern chips so that they have 32K on the 16bit buss. It's a much faster machine. 2 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted May 28, 2023 Author Share Posted May 28, 2023 Hi again Any hits are certainly appreciated. I have spent some time reading up on execution speed of the different instructions and addressing modes, as explained in the TMS 9900 Microprocessor Data Manual - 1976, page 28-29. I may be able to save a few cycles, but I think that with my algorithm the only way to reach the target (a complete pixel update within a single frame update) is to write the entire loop in MC and just do initialization using TF. As far as I can measure, the SCROLL code takes about 2/3 of the time, so it should be possible with an all-MC solution. I have spent quite a lot on hardware, since I was 'reborn', so the current system is a 2 disk setup with PEB, serial interface 32 K ram and a P-code card as well as the most important modules like Extended Basic and the Mini Memory. Getting all this stuff did not come cheap. The main problem is, that whatever I buy from American sources gets slammed with a customs fee, and transportation is also expensive. So the price usually doubles before I receive anything bought on Ebay. The UK, as a source, is a problem for the same reasons, since they left the EU. Elsewhere there is not much on offer. Besides, not much of interest is on offer anyway. For one thing, there are no TF modules on the market, it seems. In any case I do most of my programming on the Classic99 emulator. It is more convenient and saves wear an tear on the original hardware. regards Jesper 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.