Jump to content
IGNORED

fbForth—TI Forth with File-based Block I/O [Post #1 UPDATED: 06/05/2024]


Lee Stewart

Recommended Posts

Interrupt Processing in fbForth—

 

I am trying to get a clear understanding of how user interrupt service routine's (ISR's) are processed in fbForth (inherited from TI Forth). The following is the ISR that gets processed when the console ISR is properly hooked by the user:

 

 

 

;[*** INTERRUPT SERVICE =============================================
* This routine is executed only if the user has installed an ISR
* using the following steps in the following order:

*   (1) Write an ISR with entry point, say MYISR.
*   (2) Determine code field address of MYISR with this high-level Forth:
*           ' MYISR CFA
*   (3) Write CFA of MYISR into user variable ISR
*   (4) Write contents of user variable INTLNK into >83C4, the console
*       ROM's ISR hook.  INTLNK will have the address of INT1 below.

* Steps (2)-(4) in high-level Forth are shown below:
*           ' MYISR CFA
*           ISR !
*           INTLNK @ HEX 83C4 !

* The console ISR branches to >83C4 if it is non-zero, which it is with the 
* user's ISR properly installed.  This means that the console ISR will branch
* to INT1 with BL *R12 from WP = GPLWS (>83E0), R12 containing INT1 below.

* The four lines of code at INT1 modify 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 in Forth's inner interpreter.
*** =================================================================

* 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.

INT1   LI   TEMP1,INT2              Load entry point, INT2
       MOV  TEMP1,@2*NEXT+MAINWS    Copy it to Forth's NEXT (R15)
       LWPI >83C0                   Change to interrupt WS  {EDIT: as console ISR would do}
       RTWP                         Return to {EDIT: caller of} console ISR {EDIT: NOT to console ISR!}

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         set "pending interrupt" flag
       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         clear "pending interrupt" flag
       MOVB @>83D4,TEMP0
       SRL  TEMP0,8
       AI   TEMP0,>100
       MOVB @VDPSTA,TEMP1   remove pending interrupt
       BLWP @VWTR
       LIMI 2
       B    *NEXT           continue normal task
;]*===========================================================

 

 

 

One line I clearly do not understand is line 33 because the console ROM's ISR got there from the GPL workspace (>83E0), not the ISR workspace (>83C0)! When it returns to the console ISR, the next instruction is to clear the GPLWS R8; but, now, it's clearing R8 of the ISR WS!! Perhaps, it doesn't matter; but, if it does, I've found a bug in fbForth/TI-Forth's user interrupt processing (see console ISR code excerpt from Heiner Martin's book below).

 

{EDIT: See lines 33-34 in code above for edits. I now understand, I think.}

0AA8  LWPI >83E0       GPLWS
      AB   R14,@>8379  VDP interrupt timer (system flags!)
      MOV  @>83C4,R12  User defined interrupt
      JEQ  >0AB8       None, then jump
      BL   *R12        Otherwise execute
0AB8  CLR  R8          Clear GROM search pointer
      LWPI >83C0       INTWS
0ABE  RTWP             And end interrupt 

Any ideas?

 

...lee

 

Edited by Lee Stewart
Link to comment
Share on other sites

I still want to answer the question in my last post; but, I have a more pressing problem, viz., I desperately want/need to contrive a way to loop out from ALC to high-level Forth and back to the ALC where I left it. This will save me a lot of duplication and, of course, space. I expect that I will need to co-opt Forth's inner interpreter in a way similar to that of the ISR that is the subject of my last post, but not via NEXT. Perhaps like @Willsy's method of executing compiled Forth code at HERE (back a few posts)?

 

...lee

Link to comment
Share on other sites

Hmmm.... I think you'll need something like (psuedo code):

    <some assembler code that you are executing...>
    ...
    ...
    ...
    ; now we need to jump into Forth code...
    ; first, place the resume address on the return stack:
    li r0,resume    ; we want to resume from address "resume" afterwards
    dect rp         ; make space on return stack
    mov r0,*rp      ; place resume address on return stack
    li r0,forth_add ; address of some forth code to execute
    mov *r0,r6      ; get its cfa
    b *r6           ; go execute it
at this point, the forth code is executing. When the high-level forth code returns (via EXIT) it will get the return address from the return stack that we placed there (see above)
    ; we resume execution here, via the return stack after executing the Forth word
    ; this code will be called via NEXT, so it needs a CFA:
resume data $+2     ; needs a code pointer, as it's called via inner interpreter
    <resuming machine code>

I'm sure you get the idea.

Link to comment
Share on other sites

Regarding the ISR, there be dragons, that's for sure!

    INT1   LI   TEMP1,INT2       Load entry point, INT232.
    MOV  TEMP1,@2*NEXT+MAINWS    Copy it to Forth's NEXT (R15)      
    LWPI >83C0                   Change to interrupt WS  ????
    RTWP                         Return to console ISR

This is very obtuse code. I need to recourse back to ED/AS manual!

 

 


RTWP: "Replaces the contents of the Workspace Pointer Register with the contents of the current Workspace Register 13. Replaces the contents of the Program Counter with the contents of the current Workspace Register 14. Replaces the contents of the Status Register with the contents of the current Workspace Register 15. The effect of this instruction is to restore the execution environment that existed prior to an interrupt, a BLWP instruction, or an XOP instruction."

 

So, it loads the WS reg with >83C0, *then* does a RTWP. Soooo.... RTWP will look in....

 

>83C0 + 2610 = >83DA for its actual workspace address*

>83C0 + 2810 = >83DC for its program counter

>83C0 + 3010 = >83DE for its status register

 

So, they are intercepting the return of the ISR by having RTWP return somewhere else, and (probably) with a different workspace. So you need to examine those memory locations and see what's in 'em to see where it's going upon encountering that RTWP. My guess is that they *don't* want to return to *console* ISR, because (at that point) there's nothing in there of any relevance.

 

HTH, or at least gets you on the right track.

 

* 26 being the offset (in bytes) to get to R13.

Edited by Willsy
Link to comment
Share on other sites

Regarding the ISR, there be dragons, that's for sure!

INT1   LI   TEMP1,INT2              Load entry point, INT2
       MOV  TEMP1,@2*NEXT+MAINWS    Copy it to Forth's NEXT (R15)      
       LWPI >83C0                   Change to interrupt WS  ????
       RTWP                         Return to console ISR

This is very obtuse code. I need to recourse back to ED/AS manual!

.

.

.

HTH, or at least gets you on the right track.

 

It does help, indeed. I now know what's going on: Since the above is the ALC that executes near the end of the console ROM's ISR, it merely duplicates the console ISR's remaining code (almost), thereby cutting out the middle man and returning to the console ISR's caller itself. Now my question has morphed into, "Why did the TI programmers not see the need to zero GPLWS R8 like the following (as in the console ISR)? (Note the added line 3 and changed comment in line 5.):

INT1   LI   TEMP1,INT2              Load entry point, INT2
       MOV  TEMP1,@2*NEXT+MAINWS    Copy it to Forth's NEXT (R15)      
       CLR  R8                      Clear GROM search pointer  <--we should still be using GPLWS (>83E0) at this point
       LWPI >83C0                   Change to interrupt WS
       RTWP                         Return to console ISR's caller (NOT the console ISR)

...lee

Link to comment
Share on other sites

 

Why did the TI programmers not see the need to zero GPLWS R8 like the following (as in the console ISR)?

 

 

Well, because to err is human and to write crappy code is a particular form of that. We do have some bugs and bad designs in the TI ROMs, like the DSRLNK that is already in the console but always returns to the GPL interpreter, or the bug in the TI disk controller that produces unexpected bytes in the preamble, or the cassette routine that just happens to work correctly but actually has a big flaw in it.

  • Like 1
Link to comment
Share on other sites

Well, because to err is human and to write crappy code is a particular form of that. We do have some bugs and bad designs in the TI ROMs, like the DSRLNK that is already in the console but always returns to the GPL interpreter, or the bug in the TI disk controller that produces unexpected bytes in the preamble, or the cassette routine that just happens to work correctly but actually has a big flaw in it.

 

I have one more question regarding the clearing (or lack thereof) of R8 in the GPLWS within the console ISR: In the following full excerpt of the console ISR from TI99/4A INTERN, Heiner Martin's comment on line 226 (CLR R8) is "Clear GROM search pointer"; but, the E/A Manual says the GROM search pointer is in the INTWS. Which one is correct? And, if the the E/A Manual is correct, why clear GPLWS R8 (>83F0) just before ending interrupt processing?

 

 

 

Interrupt routine

0900 0300 LIMI >0000            Disable interrupt
0902 0000
0904 02E0 LWPI >83E0            Load GPLWS!
0906 83E0
0908 04CC CLR  12               Clear CRU
090A 23A0 COC  @>0032,14        Cassette interrupt?
090C 0032
090E 1602 JNE  >0914            No, jump
0910 0460 B    @>1404
0912 1404
0914 1F02 TB   >0002
0916 1619 JNE  >094A            Jump, if VDP interrupt
0918 020C LI   12,>0F00         Clear CRU
091A 0F00
091C 1D01 SBO  >0001
091E 1E00 SBZ  >0000
0920 022C AI   12,>0100
0922 0100
0924 028C CI   12,>2000
0926 2000
0928 130E JEQ  >0946            End CRU
092A 1D00 SBO  >0000
092C 9820 CB   @>4000,@>000D    ROM exists
092E 4000
0930 000D
0932 16F5 JNE  >091E            No, next
0934 C0A0 MOV  @>400C,2         Intlnk?
0936 400C
0938 13F2 JEQ  >091E            No, next ROM
093A C002 MOV  2,0
093C C0A2 MOV  @>0002(2),2      Fetch INT address
093E 0002
0940 0692 BL   *2               And execute
0942 C090 MOV  *0,2             Next Int routine
0944 10F9 JMP  >0938
0946 0460 B    @>0AB8           End interrupt from CRU
0948 0AB8
094A 1D02 SBO  >0002            Clear VDP interrupt
094C D060 MOVB @>83C2,1         Fetch interrupt flag byte
094E 83C2
0950 0A11 SLA  1,1              No interrupt permitted
0952 1702 JNC  >0958
0954 0460 B    @>0A84           Then jump
0956 0A84
0958 0A11 SLA  1,1
095A 1846 JOC  >09E8            No sprite move permitted, then jump
095C D320 MOVB @>837A,12        Number sprites
095E 837A
0960 1343 JEQ  >09E8            No sprite end
0962 098C SRL  12,8
0964 0202 LI   2,>8800          VDP RD
0966 8800
0968 0203 LI   3,>8C00          VDP WD
096A 8C00
096C 0208 LI   8,>0780          Sprite motion table
096E 0780
0970 D7E0 MOVB @>83F1,*15       Write address motion table
0972 83F1
0974 D7C8 MOVB 8,*15
0976 04C4 CLR  4
0978 D112 MOVB *2,4             Datas Y velocity
097A 04C6 CLR  6
097C D192 MOVB *2,6             Datas X velocity
097E 0844 SRA  4,4
0980 D152 MOVB *2,5             Auxiliary datas
0982 0845 SRA  5,4
0984 A144 A    4,5
0986 D1D2 MOVB *2,7
0988 0846 SRA  6,4
098A 0847 SRA  7,4
098C A1C6 A    6,7
098E 0228 AI   8,>FB80          Address sprite descriptor table
0990 FB80
0992 D7E0 MOVB @>83F1,*15       Write address
0994 83F1
0996 D7C8 MOVB 8,*15
0998 04C4 CLR  4
099A D112 MOVB *2,4             Fetch position
099C A105 A    5,4
099E 0284 CI   4,>C0FF
09A0 C0FF
09A2 1209 JLE  >09B6
09A4 0284 CI   4,>E000          Compute new position
09A6 E000
09A8 1B06 JH   >09B6
09AA C145 MOV  5,5
09AC 1502 JGT  >09B2
09AE 0224 AI   4,>C000
09B0 C000
09B2 0224 AI   4,>2000
09B4 2000
09B6 04C6 CLR  6
09B8 D192 MOVB *2,6
09BA A187 A    7,6
09BC 0268 ORI  8,>4000          VDP address for writing
09BE 4000
09C0 D7E0 MOVB @>83F1,*15
09C2 83F1
09C4 D7C8 MOVB 8,*15
09C6 D4C4 MOVB 4,*3             Write positions
09C8 0228 AI   8,>0482
09CA 0482
09CC D4C6 MOVB 6,*3
09CE 06C5 SWPB 5
09D0 D7E0 MOVB @>83F1,*15       Write address motion table
09D2 83F1
09D4 D7C8 MOVB 8,*15
09D6 0945 SRL  5,4
09D8 D4C5 MOVB 5,*3             Write auxiliary values
09DA 06C7 SWPB 7
09DC 0947 SRL  7,4
09DE D4C7 MOVB 7,*3
09E0 0228 AI   8,>C002          New address motion table
09E2 C002
09E4 060C DEC  12               Last sprite?
09E6 15C4 JGT  >0970            No, once again
09E8 0A11 SLA  1,1
09EA 183D JOC  >0A66            No sound process jump
09EC D0A0 MOVB @>83CE,2         Number of sound byte
09EE 83CE
09F0 133A JEQ  >0A66            None, then end
09F2 780E SB   14,@>83CE        -1
09F4 83CE
09F6 1637 JNE  >0A66            Not 0, then end
09F8 C0E0 MOV  @>83CC,3         Pointer sound list
09FA 83CC
09FC C14E MOV  14,5
09FE 0915 SRL  5,1              GROM or VDP?
0A00 180A JOC  >0A16            1=VDP, then jump
0A02 06A0 BL   @>0864           Push GROM address on substack
0A04 0864
0A06 0205 LI   5,>0402
0A08 0402
0A0A A14D A    13,5             GROM write address
0A0C D543 MOVB 3,*5             Write GROM address
0A0E D560 MOVB @>83E7,*5
0A10 83E7
0A12 C18D MOV  13,6             Read address
0A14 1007 JMP  >0A24
0A16 0205 LI   5,>8C02          VDPWA
0A18 8C02
0A1A D560 MOVB @>83E7,*5        Write VDP address
0A1C 83E7
0A1E D543 MOVB 3,*5
0A20 0206 LI   6,>8800          VDPRD
0A22 8800
0A24 D216 MOVB *6,8             Fetch byte
0A26 130F JEQ  >0A46            0?
0A28 9220 CB   @>0A9C,8
0A2A 0A9C
0A2C 130A JEQ  >0A42            >FF? Yes,switch to another(well possible)!
0A2E 0988 SRL  8,8              Number
0A30 A0C8 A    8,3              To address
0A32 D816 MOVB *6,@>8400        Load sound process
0A34 8400
0A36 0608 DEC  8                How many bytes?
0A38 16FC JNE  >0A32            Next byte
0A3A 05C3 INCT 3
0A3C D096 MOVB *6,2             Fetch duration
0A3E 1309 JEQ  >0A52
0A40 1009 JMP  >0A54            Go on
0A42 2BA0 XOR  @>0378,14        Change system flags
0A44 0378
0A46 D0D6 MOVB *6,3             Fetch new address
0A48 0202 LI   2,>0100          Sound byte >01
0A4A 0100
0A4C D816 MOVB *6,@>83E7        Complete address
0A4E 83E7
0A50 1001 JMP  >0A54            Once again
0A52 7082 SB   2,2
0A54 C803 MOV  3,@>83CC         New pointer sound list
0A56 83CC
0A58 D802 MOVB 2,@>83CE         Sound byte
0A5A 83CE
0A5C 0285 CI   5,>8C02          From VDP?
0A5E 8C02
0A60 1302 JEQ  >0A66
0A62 06A0 BL   @>0842           POP GROM address from substack
0A64 0842
0A66 0A11 SLA  1,1
0A68 180D JOC  >0A84            No QUIT key, then jump
0A6A 020C LI   12,>0024         Load CRU
0A6C 0024
0A6E 30E0 LDCR @>0012,3
0A70 0012
0A72 0B7C SRC  12,7
0A74 020C LI   12,>0006
0A76 0006
0A78 3605 STCR 5,8              Fetch CRU
0A7A 2560 CZC  @>004C,5         QUIT key?
0A7C 004C
0A7E 1602 JNE  >0A84
0A80 0420 BLWP @>0000           Software reset
0A82 0000
0A84 D82F MOVB @>FC00(15),@>837B    VDP status in copy RAM
0A86 FC00
0A88 837B
0A8A 02E0 LWPI >83C0            INTWS
0A8C 83C0
0A8E 05CB INCT 11               Screen timeout counter
0A90 160B JNE  >0AA8            Not 0

Interrupt level 2:

0A92 D30A MOVB 10,12            VDP register 1
0A94 098C SRL  12,8
0A96 026C ORI  12,>8160         Basis value
0A98 8160
0A9A 024C ANDI 12,>FFBF         Turn off screen
0A9C FFBF
0A9E D820 MOVB @>83D9,@>8C02    Load VDP register
0AA0 83D9
0AA2 8C02
0AA4 D80C MOVB 12,@>8C02
0AA6 8C02
0AA8 02E0 LWPI >83E0            GPLWS
0AAA 83E0
0AAC B80E AB   14,@>8379        VDP interrupt timer (system flags!)
0AAE 8379
0AB0 C320 MOV  @>83C4,12        User defined interrupt
0AB2 83C4
0AB4 1301 JEQ  >0AB8            None, then jump
0AB6 069C BL   *12              Otherwise execute
0AB8 04C8 CLR  8                Clear GROM search pointer
0ABA 02E0 LWPI >83C0            INTWS
0ABC 83C0
0ABE 0380 RTWP                  And end interrupt

 

 

 

...lee

Link to comment
Share on other sites

I'm sure you get the idea.

 

I get the idea—I just can't quite wrap my head around the code, yet. Executing B *NEXT won't pop the return stack as near as I can tell. It looks like the only part of the fbForth interpreter (see ALC below) that does that is at $SEMIS and usually only gets executed at the end of a colon definition. I think I need to preserve the current IP on the return stack before I do anything with the cfa of the Forth word I want to execute from ALC. Then, of course, I need to figure a way back.

DODOES DECT SP             <--Starts at >832E in scratchpad RAM
       MOV  W,*SP
       MOV  LINK,W
DOCOL  DECT R
       MOV  IP,*R
       MOV  W,IP
$NEXT  MOV  *IP+,W          NEXT (R15) points here
DOEXEC MOV  *W+,R1
       B    *R1
$SEMIS MOV  *R+,IP          SEMIS ( CFA of Forth word, ;S ) points here
       MOV  *IP+,W
       MOV  *W+,R1
       B    *R1

I still think the key is to do something like the device used in the user ISR hook code in the spoiler I posted in #578 above that gets from INT2 to INT3.

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

...

I still think the key is to do something like the device used in the user ISR hook code in the spoiler I posted in #578 above that gets from INT2 to INT3.

 

...something like this, perhaps:

* << Save somewhere the info for return to ALC caller >>

       DECT R               reserve spot on return stack
       MOV  IP,*R           move current IP to return stack
       LI   IP,RTPROC       move address of return procedure to IP
       MOV  FR_CFA,W        move CFA of Forth routine to W
       B    @DOEXEC         do the Forth routine

RTPROC DATA $+2
       DATA $+2
       MOV  *R+,IP          restore previous IP

* << Retrieve ALC return info and return to caller.. >>
* << ...caller will execute B *NEXT when it finishes >>

...lee

Link to comment
Share on other sites

I get the idea—I just can't quite wrap my head around the code, yet. Executing B *NEXT won't pop the return stack as near as I can tell.

That's right. The return stack isn't popped by NEXT, it's popped by EXIT in the colon-definition that you call. So, EXIT pops the return address of the resume point that you push on there, loads it into the IP, and calls NEXT.

Link to comment
Share on other sites

That's right. The return stack isn't popped by NEXT, it's popped by EXIT in the colon-definition that you call. So, EXIT pops the return address of the resume point that you push on there, loads it into the IP, and calls NEXT.

 

I don't think so, Mark. If the word I call is a colon definition, DOCOL pushes the current IP to the return stack and that is what SEMIS (your EXIT) pops from the return stack. If it's a CODE definition, the return stack is unaffected. My problem is that I want to execute a Forth word from within the ALC of another Forth word. I need the existing, waiting IP to be preserved in the process so that, when I finally return to the interpreter from where I needed to loop out to Forth in the first place, the appropriate conclusion will be reached.

 

...lee

Link to comment
Share on other sites

OK, I think I've got it. The relevant routines are BLA2F (line 106) and RTA2F (line 125) in the code below. I will test them over the next day or so to see whether I really understand what's going on. Again—these two routines are to be my mechanism for executing a high-level Forth word from within the ALC of another Forth word and returning to the ALC innards of said previous word. This should help me avoid extensive rewrites of some code that is part of high-level Forth that I need in a few procedures I wish to implement in ALC as much as possible. The first of these will be the 64-column editor.

 

 

 

********************************************************************
* MYBANK must be at same location in all banks with the code that appears
* in the following table.  The corresponding values for BANK0--BANK3
* should also be in the same places in each bank.
* 
*       Bank Select MYBANK
*       ---- ------ ------
*       0    >6006  >C000
*       1    >6004  >8000
*       2    >6002  >4000
*       3    >6000  >0000
* 
* Bank0 code will look like this
*
MYBANK DATA >C000
BANK0  DATA >C000
BANK1  DATA >8000
BANK2  DATA >4000
BANK3  DATA >0000
*
* Banks 1--3 will look the same except without labels and the DATA
* instruction at MYBANK's location will correspond to its bank.
*
* Before a bank is selected, the values above will have >6000 added.
*
********************************************************************
* BLBANK
*
* General bank branching routine (32KB ROM, i.e., 4 banks) for a
* branch that is expected to return (not high-level Forth) via RTBANK---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLBANK
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLBANK DECT R               reserve space on return stack (R14)
       MOV  @2(LINK),*R     push return address
       DECT R               reserve space on return stack
       MOV  @MYBANK,*R      push return bank (leftmost 2 bits)
       MOV  *LINK,LINK      copy destination bank address to R11
       MOV  LINK,CRU        copy it to R12
       ANDI LINK,>1FFF      mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      make it a real address
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to destination address
*
********************************************************************
* RTBANK
*
* General bank return routine (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTBANK
*
RTBANK MOV  *R+,CRU         pop return bank# from return stack to R12
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       MOV  *R+,LINK        pop return address from return stack
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to return address
*
********************************************************************
* BLF2A
*
* High-level Forth to ALC bank branching routine (32KB ROM, i.e., 4
* banks) that is expected to return to bank0 via RTNEXT.  This will
* only(?) be used for the ALC payload of Forth stubs in bank0---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLF2A
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLF2A  MOV  *LINK,LINK      copy destination bank address to R11
       MOV  LINK,CRU        copy it to R12
       ANDI LINK,>1FFF      mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      make it a real address
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to destination address
*
********************************************************************
* RTNEXT
*
* High-level Forth bank "return" routine from ALC (32KB ROM, i.e., 4 
* banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTNEXT
*
RTNEXT CLR  @6006           switch to bank0
       B    *NEXT           branch to next CFA (in R15)
*
********************************************************************
* BLA2F
*
* ALC to high-level Forth bank branching routine (32KB ROM, i.e., 4
* banks) that is expected to return to calling bank via RTA2F---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLA2F
*       DATA <Forth cfa in bank0>
*
BLA2F  DECT R               reserve space on return stack 
       MOV  @2(LINK),*R     push return address of calling bank
       DECT R               reserve space on return stack
       MOV  @MYBANK,*R      push return bank# (leftmost 2 bits)
       DECT R               reserve spot on return stack
       MOV  IP,*R           move current IP to return stack
       LI   IP,RTA2F        move address of return procedure to IP
       MOV  *LINK,W         move CFA of Forth routine to W
       CLR  @>6006          switch to bank0
       B    @DOEXEC         Execute the Forth routine
*
********************************************************************
* RTA2F
*
* ALC to high-level Forth bank "return" routine from Forth to calling
* ALC (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called through B *NEXT at end of Forth word's execution in BLA2F
*
RTA2F  DATA $+2             stored in IP by BLA2F (points to W, next instruction)
       DATA $+2             stored in W by NEXT (points to "code field", next instruction)
       MOV  *R+,IP          restore previous IP ("code field" executed by NEXT)
* Retrieve ALC return info and return to caller...
* ...caller will execute B *NEXT when it finishes
       B    @RTBANK         branch to general bank return routine above 

 

 

 

...lee

 

{EDIT #1: I had some of the lines of BLA2F out of order!}

{EDIT #2: The above code is flawed where it uses MOV @2(LINK),*R !! See next post for the correct code.}

Edited by Lee Stewart
Link to comment
Share on other sites

Executing Forth words from ALC with bank-switching—

 

I made a serious error (same one twice!) in my previous post that incremented an address contained in a register and pushed the contents of the new address to the return stack; but, what I really wanted was the incremented address. You can see the bad code in my last post ( MOV @2(LINK),*R ).

 

The following routines now work properly to implement execution of Forth words from within ALC, returning to the calling ALC when done. All the routines except BLBANK were tested. BLBANK cannot be tested until a cartridge ROM is actually simulated and I'm not there yet. BLF2A's bank-switching code was not tested; but, the actual execution of the ALC part of a Forth word was tested.

 

 

 

********************************************************************
* MYBANK must be at same location in all banks with the code that appears
* in the following table.  The corresponding values for BANK0--BANK3
* should also be in the same places in each bank.
* 
*       Bank Select MYBANK
*       ---- ------ ------
*       0    >6006  >C000
*       1    >6004  >8000
*       2    >6002  >4000
*       3    >6000  >0000
* 
* Bank0 code will look like this
*
MYBANK DATA >C000
BANK0  DATA >C000
BANK1  DATA >8000
BANK2  DATA >4000
BANK3  DATA >0000
*
* Banks 1--3 will look the same except without labels and the DATA
* instruction at MYBANK's location will correspond to its bank.
*
* Before a bank is selected, the values above will be shifted right 13
* bits and have >6000 added.
*
********************************************************************
* BLBANK
*
* General bank branching routine (32KB ROM, i.e., 4 banks) for a
* branch that is expected to return (not high-level Forth) via RTBANK---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLBANK
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLBANK DECT R               reserve space on return stack (R14)
       MOV  *LINK+,CRU      copy destination bank address to R12
       MOV  LINK,*R         push return address
       DECT R               reserve space on return stack
       MOV  @MYBANK,*R      push return bank (leftmost 2 bits)
       MOV  CRU,LINK        copy destination bank address to R11
       ANDI LINK,>1FFF      mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      make it a real address
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to destination address
*
********************************************************************
* RTBANK
*
* General bank return routine (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTBANK
*
RTBANK MOV  *R+,CRU         pop return bank# from return stack to R12
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       MOV  *R+,LINK        pop return address from return stack
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to return address
*
********************************************************************
* BLF2A
*
* High-level Forth to ALC bank branching routine (32KB ROM, i.e., 4
* banks) that is expected to return to bank0 via RTNEXT.  This will
* only(?) be used for the ALC payload of Forth stubs in bank0---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLF2A
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLF2A  MOV  *LINK,LINK      copy destination bank address to R11
       b    *link       <<<temporarily not switching banks>>>
       
       MOV  LINK,CRU        copy it to R12
       ANDI LINK,>1FFF      mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      make it a real address
       SRL  CRU,13          shift bank# into bits 1-2 of R12
       AI   CRU,>6000       make it a real bank-switch address
       CLR  *CRU            switch to destination bank
       B    *LINK           branch to destination address
*
********************************************************************
* RTNEXT
*
* High-level Forth bank "return" routine from ALC (32KB ROM, i.e., 4 
* banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTNEXT
*
RTNEXT CLR  @6006           switch to bank0
       B    *NEXT           branch to next CFA (in R15)
*
********************************************************************
* BLA2F
*
* ALC to high-level Forth bank branching routine (32KB ROM, i.e., 4
* banks) that is expected to return to calling bank via RTA2F---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLA2F
*       DATA <Forth cfa in bank0>
*
BLA2F  DECT R               reserve space on return stack 
       MOV  *LINK+,W        move CFA of Forth routine to W
       MOV  LINK,*R         push return address of calling bank
       DECT R               reserve space on return stack
       MOV  @MYBANK,*R      push return bank# (leftmost 2 bits)
       DECT R               reserve spot on return stack
       MOV  IP,*R           move current IP to return stack
       LI   IP,RTA2F        move address of return procedure to IP
       CLR  @>6006          switch to bank0
       B    @DOEXEC         Execute the Forth routine
*
********************************************************************
* RTA2F
*
* ALC to high-level Forth bank "return" routine from Forth to calling
* ALC (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called through B *NEXT at end of Forth word's execution in BLA2F
*
RTA2F  DATA $+2             stored in IP by BLA2F (points to W, next instruction)
       DATA $+2             stored in W by NEXT (points to "code field", next instruction)
       MOV  *R+,IP          restore previous IP ("code field" executed by NEXT)
* Retrieve ALC return info and return to caller...
* ...caller will execute B *NEXT when it finishes
       B    @RTBANK         branch to general bank return routine above
*
******************************************************************** 

 

 

 

In a bit, I will post the test code that successfully uses all of the routines except BLBANK.

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

OK—The following code tests the branching routines from my last post that allow looping out to/from Forth from/to ALC. In fbForth 1.0, ALLOT is written as high-level Forth, i.e., a list of code field addresses. In the following code, the high-level Forth (lines 5--7) is commented out with "*++ ":

 

 

 

;[*** ALLOT ***
       DATA L106D
L106E  DATA 5+TERMBT*LSHFT8+'A','LL','OT'+TERMBT

*++ ALLOT  DATA DOCOL,SPAT,OVER,HERE,PLUS,LIT,>80
*++        DATA PLUS,ULESS,TWO,QERROR,DP,PSTORE
*++        DATA SEMIS

*++ <<< temporarily replacing above to test loop outs to/from Forth from/to ALC >>>

ALLOT  DATA $+2

* let's test branching into ALC for remainder of this Forth word

       BL   @BLF2A
       DATA _ALLOT          not actually switching banks yet

* bank0 code for remainder of word ALLOT

_ALLOT DECT SP              SP@
       MOV  SP,*SP
       INCT *SP             OVER
       DECT SP
       MOV  @4(SP),*SP
       DECT SP              HERE
       MOV  @$DP(U),*SP
       A    *SP+,*SP        +
       LI   R0,>80          >80
       A    R0,*SP          +
       MOV  *SP+,R2         U<
       MOV  *SP,R1
       CLR  *SP
       C    R1,R2
       JHE  _ULESS
       INC  *SP
_ULESS DECT SP              2
       LI   R0,2
       MOV  R0,*SP

* let's test calling into Forth with ?ERROR

       BL   @BLA2F
       DATA QERROR

* resume ALC

       A    *SP+,@$DP(U)    DP +!
       B    @RTNEXT
;]* 

 

 

 

...lee

Link to comment
Share on other sites

Graphics considerations—

 

While working on the 64-column editor, I must deal with some of the graphics capabilities of the TI-99/4A. As I may have indicated somewhere above, I am attempting to put the VDP modes and graphics primitives into the same cartridge ROM bank as the 64-column editor. While looking at what the original TI Forth programmers did with mode changes, I noticed that non-text modes are initialized with video register #1 set to E0h except for multicolor mode, which is set to EBh! This sets sprites to double size and magnified. There is no explanation as to why this is set as the default. There is no such recommendation for multicolor mode in the E/A manual that I can find. I should think E8h would be fine. Is there any particular reason for doing this?

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

More Graphics considerations—

 

While looking at consolidating some of the graphics functions in fbForth, I realized that Forth stubs for the seven mode-changing functions would consume at least 138 bytes in bank0. If I adopt the method used in TurboForth, I could reduce that hit to just 16 bytes with GMODE . I could then include an optional compatibility set of Forth words in FBLOCKS for those existing(?) TI Forth programs that might require their use. For example, TEXT80 and GRAPHICS2 would simply be defined as

: TEXT80  ( --- )
   0 GMODE
;

: GRAPHICS2  ( --- )
   4 GMODE
;

TEXT80 would consume 18 bytes of dictionary space and GRAPHICS2 would eat up 22.

 

Perhaps I should call it something other than GMODE because the mode values in fbForth do not correspond to those in TurboForth from whence the idea comes. VMODE might be good because there is already a VDPMDE user variable that stores the value of the current graphics mode.

 

Any suggestions?

 

...lee

Link to comment
Share on other sites

Yet more Graphics considerations (Screen Fonts)—


I'm probably throwing requests for suggestions/criticisms at everyone way too fast to get responses to all of them; but, here's another one anyway: While thinking about managing the display character font, it occurred to me that users might want to use their own fonts on screen. One question is how to indicate to fbForth where the font is located for reloading when graphics modes are changed. I could certainly use a file like the character set files used with TI Writer (CHARA1, CHARA2, ...). The most convenient way for me to set up using one of those files is to convert it to DF128 format from the PROGRAM format it's in—no content change, just a header change—so I can use them as blocks files. The user could indicate in the load block (block 1) the relevant font file information in a format I will work out for that purpose.


The only other question I have is about the format of the font file. I don't know whether there is a general format because I've never worked with them directly before. It looks like the font starts six bytes in for character 0 (NUL) and ends two bytes before the end with character 126 ('~'). I can deal with that if it's always that layout. But, do the leading 6 bytes and trailing 2 bytes have some meaning I should know about?


Any thoughts?


...lee

Link to comment
Share on other sites

Yet more Graphics considerations (Screen Fonts...continued)—
While perusing issues of Micropendium, I discovered what many of you no doubt knew a long time ago: The first 3 words (6 bytes) of CHARAx screen font files are
  1. Flag for ???, usually 0000h.
  2. # of bytes to be copied to VRAM character Pattern Descriptor Table (PDT). [this # includes the first 6 bytes of the file!!]
  3. VRAM address to copy file. [This is 6 bytes in front of the actual PDT due to (2)!! This usually also means that the last character pattern in the file has only 2 of its 8 bytes represented. Because that is usually character 127 or 255, it's not likely a problem].

Font files intended for only characters 0 – 127 are 1024 bytes long (4 sectors) and those for characters 0 – 255 are 2048 bytes (8 sectors). FunnelWeb looks like it uses the same format, but with the 6-byte header all zeroes. FW apparently determines how many character patterns to write by the size of the file and does not need the VRAM address because FW will write to where it knows the PDT is located.

 

For fbForth, however, I am inclined to use a character set file with no header and with exactly 1024 bytes for characters 0 – 127. fbForth knows where to put them and I will provide for the user to give fbForth whatever filename s/he wishes for the desired alternate screen font. The file will need to be DF128 format per my last post.

 

I will eventually provide a Forth routine to convert a CHARA1-formatted file to an fbForth character file format, as well as a routine for editing an existing character set, including the default character set in the cartridge. The user will be on his/her own for characters 128 – 255 as has always been the case for fbForth and TI Forth before it. H-m-m-m... I suppose I could provide for loading those characters if the file is more than 1 Forth block long. It would still only load the first 128 characters in split and split2 modes. Though the higher characters could be loaded in split mode, I think I'll limit that to text, text80 and graphics modes (multicolor and bitmap modes do not use the text-type character sets).

 

I'm glad we had this little talk...

 

...lee

Link to comment
Share on other sites

I hadn't realized those were considered a standard for fonts :) It's just got the usual EA#5 style program image header on top of it in those 6 bytes. The flag word indicates whether there are more files to be loaded. :)

 

Aha! That makes more sense. I'll have to find in Micropendium where the bytes were described. I'm pretty sure what I listed was what I read. It makes much more sense that the second word is the VRAM address and the third word is the number of bytes to load from that point on. [EDIT: This would be nice; but, it appears this is not so. See my next post.]

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

I hadn't realized those were considered a standard for fonts :) It's just got the usual EA#5 style program image header on top of it in those 6 bytes. The flag word indicates whether there are more files to be loaded. :)

 

It does look like that format became a sort-of standard for the way screen fonts were handled.

 

Regarding my last post about the header bytes, what I had read was by Wayne Stith, the author of CHARA1FIX on p. 30 of the June, 1989 issue of Micropendium, was correct. He said word #2 is the number of bytes to load and word #3 is the load address. The thing that confuses me is that he talks about it being loaded into VRAM, which, of course, is where the PDT is located; but, I was unaware that EA5 format program files loaded anywhere but CPU RAM—07FAh is obviously not in CPU RAM. Perhaps they (TI programmers) merely started with that format. One thing is clear: The character patterns start at the 7th byte (byte 6).

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

That's standard for PROGRAM image header -- second word is the number of bytes and third is the address to load to. You are right of course that an EA#5 is supposed to load to CPU memory, not VDP, I didn't consider that... but I guess so much on the TI used this format or variants of it that I just considered it a defacto "memory image" standard. :)

Link to comment
Share on other sites

More Graphics mode musings—

 

I should probably dig this one out for myself but thought one of y'all might know: TI Forth (and fbForth, by extension) sets VDP register 1 (VR01) to B0h while it sets up VRAM and the other VDP registers for the selected graphics mode—except for bitmap mode. It sets VR01 to A0h for bitmap mode setup. They both blank the screen, which is the point. The only difference is that bit 3 (from the left) is on with VR01 = B0h. That sets text mode; but, with the screen blank, I don't think the mode matters. It seems to do just fine for graphics mode (32 column). After all, when the setup finishes, the mode is properly set before the screen reappears. If possible, I want to use the same screen-blanking code while I set up all VDP modes. Does anyone know whether it matters which code I use for VR01 to blank the screen during setup, B0h or A0h?

 

...lee

Edited by Lee Stewart
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...