Willsy Posted March 27, 2014 Share Posted March 27, 2014 Try: http://atariage.com/forums/topic/196951-how-long/?hl=%2Binstruction+%2Btiming&do=findComment&comment=2509605 (see post #3 from Matthew) Also here: http://atariage.com/forums/topic/183479-how-long-would-the-following-instructions-take/?hl=%2Binstruction+%2Btiming&do=findComment&comment=2303456 And here: http://atariage.com/forums/topic/170629-same-delay-in-shorter-space/?hl=%2Binstruction+%2Btiming&do=findComment&comment=2113321 Note in the above threads that it's always me that is asking the questions. How embarassing . Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted March 28, 2014 Author Share Posted March 28, 2014 Yeah, Mark. Now I remember! And, you should never be embarrassed to ask questions. It's often the quickest learning method. Or, in my case all too often ('cause I'm old!), relearning! ...lee Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted March 29, 2014 Author Share Posted March 29, 2014 (edited) 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 March 31, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted March 30, 2014 Author Share Posted March 30, 2014 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 Quote Link to comment Share on other sites More sharing options...
Willsy Posted March 31, 2014 Share Posted March 31, 2014 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 itat 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. Quote Link to comment Share on other sites More sharing options...
Willsy Posted March 31, 2014 Share Posted March 31, 2014 (edited) 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 March 31, 2014 by Willsy Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted March 31, 2014 Author Share Posted March 31, 2014 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 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted March 31, 2014 Share Posted March 31, 2014 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. 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted March 31, 2014 Author Share Posted March 31, 2014 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 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 1, 2014 Author Share Posted April 1, 2014 (edited) 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 April 1, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 1, 2014 Author Share Posted April 1, 2014 ... 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 Quote Link to comment Share on other sites More sharing options...
Willsy Posted April 1, 2014 Share Posted April 1, 2014 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. Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 1, 2014 Author Share Posted April 1, 2014 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 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 1, 2014 Author Share Posted April 1, 2014 (edited) 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 April 2, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 2, 2014 Author Share Posted April 2, 2014 (edited) 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 April 2, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 2, 2014 Author Share Posted April 2, 2014 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 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 6, 2014 Author Share Posted April 6, 2014 (edited) 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 April 6, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 8, 2014 Author Share Posted April 8, 2014 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 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 9, 2014 Author Share Posted April 9, 2014 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 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 9, 2014 Author Share Posted April 9, 2014 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 Flag for ???, usually 0000h. # of bytes to be copied to VRAM character Pattern Descriptor Table (PDT). [this # includes the first 6 bytes of the file!!] 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 Quote Link to comment Share on other sites More sharing options...
Tursi Posted April 9, 2014 Share Posted April 9, 2014 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. Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 9, 2014 Author Share Posted April 9, 2014 (edited) 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 April 9, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 9, 2014 Author Share Posted April 9, 2014 (edited) 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 April 9, 2014 by Lee Stewart Quote Link to comment Share on other sites More sharing options...
Tursi Posted April 9, 2014 Share Posted April 9, 2014 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. Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted April 12, 2014 Author Share Posted April 12, 2014 (edited) 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 April 12, 2014 by Lee Stewart 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.