Jump to content
IGNORED

Scrolling using TurboForth


Recommended Posts

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

  • Like 4
Link to comment
Share on other sites

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

  • Like 2
Link to comment
Share on other sites

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:

  1. Place ALC entry point at >83C4.
  2. Current workspace (WS) is >83E0 (GPLWS).
  3. Change to your WS if needed.
  4. Do your interrupt stuff.
  5. Make >83E0 the current WS.
  6. 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

  • Like 3
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

  • Like 1
Link to comment
Share on other sites

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

  1. Interrupts are not enabled within the user ISR by you or a called function;
  2. You 
    1. Realize the GPL workspace (WS) is current,
    2. Change to TurboForth’s WS ( $8300 LWPI, ), or
    3. Change to your WS (slowest, if not in scratchpad RAM)
  3. End with
    1. RT, (realizing R8 of current WS will be cleared by console ISR) or
    2. $83C0 LWPI,  (console ISR’s WS) followed by RTWP, (returns to interrupted routine, neither clearing current R8 nor returning to console ISR)

...lee

  • Like 2
Link to comment
Share on other sites

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

 

Link to comment
Share on other sites

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

  • Like 2
Link to comment
Share on other sites

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.

  • Like 3
Link to comment
Share on other sites

  • 4 weeks later...

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

  • Like 2
Link to comment
Share on other sites

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
;

 

  • Like 2
Link to comment
Share on other sites

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

image.png.39df4535e1ad8664878a77341faaf128.png

  • Like 1
Link to comment
Share on other sites

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

  • Like 2
Link to comment
Share on other sites

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, 

   

 

 

 

  • Like 3
Link to comment
Share on other sites

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.

 

 

  • Like 3
Link to comment
Share on other sites

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

  • Like 1
Link to comment
Share on other sites

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. 

  • Like 2
Link to comment
Share on other sites

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

  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...