jschultzpedersen Posted July 5 Author Share Posted July 5 Hi again The scroll routine is now wrapped up in a single ASM word. Each call of the word scrolls 1 pixel for the entire visible string and feeds a new character every 8 scrolls. I have hooked it up to the interrupt address at $a008. This too works. However, the scrolling is not completely smooth as I kind of expected. While I have problems getting it to run at 60 frames per second with a full 33 character displayed - it runs at about 53 frames per second - which is not enough to include in an interrupt driven process, I have tested with far fewer characters displayed. But despite running at way more than the required 60 framed, it is still not smooth... if fast. Interestingly it runs smoother if placed in a loop in TF with about 29 characters displayed. I suspect the problem has to do with synchronizing with the screen update. Does this make sense? PS. How do I stop hooking up to the interrupt at $a008? Do I write a RT instruction to the address to replace the vector? The ASM code uses almost all the cpu time. So the machine is rather unresponsive, when it runs. Considering that it has to recalculate around 250 bytes, 60 times a second, I do not blame it. But it means I have to put a condition inside the scroll code to stop. Another thing that confuses me a bit is... I read that I should end my code with an RT or R11 ** b, command. However, it only works for me without such a command. Is my information obsolete, or should it be R12? regards Jesper Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 5 Share Posted July 5 1 hour ago, jschultzpedersen said: I suspect the problem has to do with synchronizing with the screen update. Does this make sense? PS. How do I stop hooking up to the interrupt at $a008? Do I write a RT instruction to the address to replace the vector? The ASM code uses almost all the cpu time. So the machine is rather unresponsive, when it runs. Considering that it has to recalculate around 250 bytes, 60 times a second, I do not blame it. But it means I have to put a condition inside the scroll code to stop. Another thing that confuses me a bit is... I read that I should end my code with an RT or R11 ** b, command. However, it only works for me without such a command. Is my information obsolete, or should it be R12? Yes if you are moving fast you would need to synchronize your code to the frame rate of the video. Lee posted this code few years back in another thread. Quote: Here is the ALC [corrected] to poll for the next VDP interrupt in TurboForth (WI , say), which must have the TMS9900 Assembler (block 9) loaded: \ Wait for VDP Interrupt ASM: WI ( -- ) R1 $8000 LI, \ VDP interrupt mask $8802 @@ R2 MOVB, \ reset VDP status byte BEGIN, $8802 @@ R2 MOVB, \ get VDP status byte R1 R2 COC, \ VDP interrupt? EQ UNTIL, \ loop if not ;ASM and, without the TMS9900 Assembler [corrected]: \ Wait for VDP Interrupt HEX CODE: WI ( -- ) 0201 8000 D0A0 8802 D0A0 8802 2081 16FC ;CODE ...lee 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted July 5 Share Posted July 5 (edited) 1 hour ago, jschultzpedersen said: PS. How do I stop hooking up to the interrupt at $a008? Do I write a RT instruction to the address to replace the vector? The ASM code uses almost all the cpu time. So the machine is rather unresponsive, when it runs. Considering that it has to recalculate around 250 bytes, 60 times a second, I do not blame it. But it means I have to put a condition inside the scroll code to stop. Another thing that confuses me a bit is... I read that I should end my code with an RT or R11 ** b, command. However, it only works for me without such a command. Is my information obsolete, or should it be R12? The user interrupt hook at $A008 should contain the entry address of an Assembly Language Code (ALC) routine that, indeed, ends with RT or B *11 because the ISR dispatcher calls the user ISR via a BL instruction and expects a return to its following instruction. You can write a Forth ALC routine as ASM: MYISR R0 R0 MOV, \ dummy entry statement \ <do other stuff> RT, \ return to caller ;ASM and put the pfa (Parameter Field Address or body address) in $A008: ' MYISR \ tick MYISR to get its cfa (Code Field Address) >BODY \ get its pfa, where ALC of MYISR starts $A008 ! \ stash entry address into user ISR hook To stop processing the user ISR, merely store a zero there. You could also store the pfa of your ISR code in a constant to later restart the user ISR. ...lee Edited July 5 by Lee Stewart clarified 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 7 Share Posted July 7 I was looking at code from the Legend of Tilda and saw something I had seen before. I found this in my library folder. It reads the CRU bit on the 9901 chip to test for vertical interval synchronization. Don't know it is any better or not. My version PUSHs and POPs R12 onto the return stack so it is safe to use in the middle of other I/O operations. Also I added an extra line from the legend of Tilda version to clear the CRU bit before testing bit 2. That could be removed but if you want to keep it here are the definitions for RPUSH, and RPOP, which should work in TForth as well. ( might need to change *RP and *RP+ ?? ) : RPUSH, ( src -- ) RP DECT, ( src) *RP MOV, ; \ compiles 4 bytes : RPOP, ( dst -- ) *RP+ SWAP MOV, ; \ compiles 2 bytes \ vsync.fth adapted from code by @PeteE on Atariage.com 12JUL2022 NEEDS MOV, FROM DSK1.LOWTOOLS HEX 8802 CONSTANT VDPSTA CODE VSYNC ( -- ) R12 RPUSH, VDPSTA @@ R12 MOVB, \ Clear interrupt flag manually R12 CLR, \ 9901 CRU base Address= 0 BEGIN, 2 TB, \ Test CRU bit 2 - VDP INT EQ UNTIL, \ Loop until set VDPSTA @@ R12 MOVB, \ Clear interrupt flag manually since we polled CRU R12 RPOP, NEXT, ENDCODE DETACH \ removes the assembler from Low RAM 4 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted July 8 Author Share Posted July 8 Hi I used the code supplied by TheBF to wait for VDP Interrupt as a word in TF ( called WI) before calling my scroll routine as well as as an integral part of the assembler code. This works fine insofar as to give a relatively stable display of the scrolling text 30 times a second. The bad news is, that the scroll routine is too slow to run 60 times a second (It does about 53 / s). So with the synch word as a delay it runs every second VDP interrupt instead. During a walk I had an idea for an alternative algorithm, that should be faster. So the next few day will show if this works out to more than 60 / s. Anyway, the information provided has given me a good idea of how to work with assorted interrupts, Thanks everybody! regards Jesper 2 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted July 14 Author Share Posted July 14 Hi again My scrolling project seems to have hit it highwater mark. I can execute 800 scrolls in about 11 seconds with 27 characters. The ASM code is divided into five words. These words are executed from within a loop in TurboForth. This is faster than 60 times a second, but obviously the computer has other things to attend to besides running my code. So turning the words into one ASM word and executing it via interrupt is not possible for me. The system locks up after the first scroll. The good news is, that it runs fine when synchronized with the WI (wait for VDP interrupt) routine published earlier. 800 loops run in about 13 seconds with a fairly stable movement and only an occasional stutter. This matches 60 updates per second. So I guess I made it! The tricks and tips provided by your comments have helped a lot reaching the final result, which spans 6 blocks. If anyone is interested in the code, I can publish it here. I will probably write a commented version of the code for an article in the TI*MES magazine. But it may take a year before it gets published, since there are 2 issues per year, and I have another article queued up already. My inabillty to run it using interrupt raises a question. How many instructions can you execute in an interrupt routine? I have no problem running words with, say, 15-20 instructions via interrupt. But thousands..... no way near it. regards Jesper Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 14 Share Posted July 14 Congratulations Jesper. Sounds like you had some fun with this one and gathered some insights into not just Forth but this old hardware as well. How many instructions in an interrupt. Good question. Grabbing a napkin and turning it over ... We would probably never want the interrupt to consume more that 10% of the CPU so let's just assume that as a requirement. That means in a North American machine we have 1/60 of a second, or 16.666mS / 10 = 1.666 mS to work with or 1666uS This is what kills the 9900. Cycles per instruction is ghastly. I just took a random freeze of instructions running in ROM with the E/A Cartridge plugged in. OMG look at the 2nd last instruction. 0496 045B b *R11 (12) 0070 0300 limi >0002 (16) 0074 0300 limi >0000 (16) 0078 D25D movb *R13,R9 (41) 007A 1105 jlt >0086 (8) 007C D109 movb R9,R4 (14) 007E 09C4 srl R4,12 (36) 0080 C164 mov @>0c36(R4),R5 (22) 0084 0455 b *R5 (12) 011A D120 movb @>837c,R4 (22) 011E 0A24 sla R4,2 (16) 0120 11FA jlt >0116 (8) 0122 D81D movb *R13,@>83f3 (49) 0126 0249 andi R9,>1fff (14) The average clock time for these instructions is 20.4 per instructions. Total clocks are 284 so this would run in 284 x .333uS = 95uS But this is on the 16 bit buss, so full processor speed. If our code is running in the Expansion Ram we need to add about ~25% , so this would take 119uS running on the 8 bit buss. And the average would go up to 25uS per instruction. So 1666 uS / 25 uS ~= 67 instructions. On a European machine we would have a bit more at 50Hz video rate. 2000uS / 25uS = 80 instructions for 10% of the CPU time. At least that's what my napkin says. 1 Quote Link to comment Share on other sites More sharing options...
jschultzpedersen Posted July 15 Author Share Posted July 15 Hi Ok. Less that one hundred instructions per interrupt cycle. No wonder it did not work even if I only scrolled a few characters (In fact that almost works with just two characters). I can see that if I try around 30 characters, it start running at 30 fps instead of 60 fps when using the WI routine to synchronize. That means it takes more than 1/60 of a second to process the scroll. I put a lot of effort into managing everything using registers for optimum speed. I had to use variables for a few counters, as I was running out of registers in the most complex code section (the actual scroll calculations) It paid off, since the execution speed grew from about 50 with my first attempts to eventually about 70 executions per second. It also helped, when I realised that I could process the patterns in 16 bits instead of 8 bits at a time. This saves a lot on SLA instructions though the handling of 'overflowing' bits between patterns gets a little more complicated. I have included a .txt file with the raw code if someone is interested. Notice the last block (number 50). This is where I store the text for scrolling. This is reflected in the stack parameters to the TSETUP word. (Block 50, 100 characters). regards Jesper Scroll code tf.txt 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.