Jump to content

Recommended Posts

13 minutes ago, speccery said:

Mini Memory does implement DSRs, and it even does that in GPL code.

GPL has veered me away from that DSR, as well. I believe TERMINAL EMULATOR II, also uses a device DSR, "SPEECH".:grin:

  • Like 1

Share this post


Link to post
Share on other sites

The thing with GPL DSRs is that they only work from BASIC because assembly programs generally don't look for them.

  • Like 1

Share this post


Link to post
Share on other sites
19 minutes ago, Asmusr said:

The thing with GPL DSRs is that they only work from BASIC because assembly programs generally don't look for them.

Assembly sucks at DSR unless you have a GPL DSRLNK DSR from Miller Graphics.

 

This has been stated for over 20 years as a fact.

 

Assembly DSR only find ROM DSR and nothing else, hence why the suck.

  • Like 2

Share this post


Link to post
Share on other sites
1 hour ago, Asmusr said:

The thing with GPL DSRs is that they only work from BASIC because assembly programs generally don't look for them.

I have written the SD card DSR for my ET-PEB board (and before that for the FPGA system). I suppose that has been the 'easy' part. The receiving end of a DSR is just parsing the PAB, and trying to do the right thing. My DSR does not properly deal with all the weird single character named DSR routines (named hex 10 to hex 16) for sector I/O.

But calling the DSRs is another matter.

A related matter is that I still don't know how to call machine code from GPL, or vice versa, and this is something I should learn at some point rather soon. If I have understood correctly, all the various DSRLNK routines are simple header walking routines, resolving a string to an address. If the address is in ROM, it's all good to go and ready for a branch to that address, but if it is a GROM address I suspect some setup is needed for scratchpad variables before jumping to somewhere in ROM to start the GPL interpreter. And I don't know how to return from there.

Share this post


Link to post
Share on other sites

I never found out where the DSRLNK that BASIC uses is located, and why we don't use that from assembly. Is it because it's written in GPL?

  • Like 1

Share this post


Link to post
Share on other sites

It's because it returns into GPL. It would have been so easy for TI to do it properly, but no.

  • Like 3
  • Sad 1

Share this post


Link to post
Share on other sites
Posted (edited)
2 hours ago, Asmusr said:

I never found out where the DSRLNK that BASIC uses is located, and why we don't use that from assembly. Is it because it's written in GPL?

 

I use it in fbForth 2.0 via the MG DSRLNK/GPLLNK. The trick is to get the GPL code to return to ALC, which is what the MG code does. The GPL DSRLNK is located in the console GROM 0 at >03D9 (see Heiner Martin’s TI99/4A Intern, p. 104). Not only does it check through all the DSRs in CRU space (>4000), but, if it exhausts CRU space, it drops through to GSRLNK, which checks every GROM (0 – 15?) at the GROM base address in GPLWS R13, which includes the cassette tape DSR in console GROM 0. I do not know whether it tries to check other GROM bases—I have not analyzed it that far.

 

FYI, here is the MG DSRLNK/GPLLNK I modified slightly to use in fbForth 2.0:

Spoiler
LLVSPT   ; <--This is the source copy location for the rest of this code.

$BUFF  EQU  >2010
 
* 4 I/O buffers below ($LO = >3020) 
* Change '4' to number of buffers needed and for which there is room.

$LO    EQU  4*>404+$BUFF      start of low-level routines after I/O buffers

*  EQUating labels below to $LO+$-LLVSPT insures those labels refer to the
*  addresses where they get copied.
*    .
*    .
*    .
*    ________  __   __   _  ____ __           __  ________
*   / ___/ _ \/ /  / /  / |/ / //_/          /  |/  / ___/
*  / (_ / ___/ /__/ /__/    / ,<      _ _ _ / /|_/ / (_ / 
*  \___/_/  /____/____/_/|_/_/|_|    (_|_|_)_/  /_/\___/  
* 
*-----------------------------------------------------------------------*
;[*== GPLLNK- A universal GPLLNK - 6/21/85 - MG =========================
* {LES NOTE: Some labels have been modified for fbForth compatibility.} *
*                                                                       *
* This routine will work with any GROM library slot since it is         *
* indexed off of R13 in the GPLWS.  (It does require Mem Expansion)     *
* This GPLLNK does NOT require a module to be plugged into the          *
* GROM port so it will work with the Editor/Assembler,                  *
* Mini Memory (with Mem Expansion), Extended Basic, the Myarc           *
* CALL LR("DSKx.xxx") or the CorComp Disk Manager Loaders.              *
* It saves and restores the current GROM Address in case you want       *
* to return back to GROM for Basic or Extended Basic CALL LINKs         *
* or to return to the loading module.                                   *
*                                                                       *
*    ENTER: The same way as the E/A GPLLNK, i.e., BLWP @GPLLNK          *
*                                                 DATA >34              *
*                                                                       *
*    NOTES: Do Not REF GPLLNK when using this routine in your code.     *
*                                                                       *
* 70 Bytes - including the GPLLNK Workspace                             *
*-----------------------------------------------------------------------*

GPLWS  EQU  >83E0                GPL workspace
G_R4   EQU  GPLWS+8              GPL workspace R4
G_R6   EQU  GPLWS+12             GPL workspace R6
SUBSTK EQU  >8373                GPL Subroutine stack pointer
LDGADR EQU  >60                  Load & Execute GROM address entry point
XTAB27 EQU  >200E                Low Mem XML table location 27
*                                ..Will contain XMLRTN at startup
GETSTK EQU  >166C              
 
*++------------------------------------------------------------------------++*
*++---ADDRESS: >3784 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
GPLLNK EQU  $LO+$-LLVSPT
       DATA GLNKWS      R7       Set up BLWP Vectors
       DATA GLINK1      R8     
* RTNADR EQU  $LO+$-LLVSPT    <---don't think we need this label
       DATA XMLRTN      R9       address where GPL XML returns to us...
*                                ...this address will already be in XTAB27,...
*                                ...>200E, so don't really need it here}
GXMLAD EQU  $LO+$-LLVSPT
       DATA >176C       R10      GROM Address for GPL 'XML >27' (>0F27 Opcode)
       DATA >50         R11      Initialized to >50 where PUTSTK address resides
*++------------------------------------------------------------------------++*
*++---ADDRESS: >3776 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
GLNKWS EQU  $LO+$-LLVSPT->18     GPLLNK's workspace of which only...
       BSS  >08         R12-R15  ...registers R7 through R15 are used

*++------------------------------------------------------------------------++*
*++---ADDRESS: >3796 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
GLINK1 EQU  $LO+$-LLVSPT
       MOV  *R11,@G_R4           Put PUTSTK Address into R4 of GPL WS
       MOV  *R14+,@G_R6          Put GPL Routine Address in R6 of GPL WS
       LWPI GPLWS                Load GPL WS
       BL   *R4                  Save current GROM Address on stack
       MOV  @GXMLAD,@>8302(R4)   Push GPL XML Address on stack for GPL Return
       INCT @SUBSTK              Adjust the stack pointer
       B    @LDGADR              Execute our GPL Routine

XMLRTN EQU  $LO+$-LLVSPT
       MOV  @GETSTK,R4           Get GETSTK pointer
       BL   *R4                  Restore GROM address off the stack
       LWPI GLNKWS               Load our WS
       RTWP                      All Done - Return to Caller
;]
*     ___  _______  __   _  ____ __           __  ________
*    / _ \/ __/ _ \/ /  / |/ / //_/          /  |/  / ___/
*   / // /\ \/ , _/ /__/    / ,<      _ _ _ / /|_/ / (_ / 
*  /____/___/_/|_/____/_/|_/_/|_|    (_|_|_)_/  /_/\___/  
* 
*-----------------------------------------------------------------------*
;[*== DSRLNK - A Universal Device Service Routine Link - MG =============
* {LES NOTE: Some labels have been modified for fbForth compatibility.} *
*                                                                       *
*      (Uses console GROM 0's DSRLNK routine)                           *
*      (Do not REF DSRLNK or GPLLNK when using these routines)          *
*      (This DSRLNK will also handle Subprograms and CS1, CS2)          *
*                                                                       *
*      ENTER: The same way as the E/A DSRLNK, i.e., BLWP @DSRLNK        *
*                                                   DATA 8              *
*                                                                       *
*      NOTES: Must be used with a GPLLNK routine                        *
*             Returns ERRORs the same as the E/A DSRLNK                 *
*             EQ bit set on return if error                             *
*             ERROR CODE in caller's MSB of Register 0 on return        *
*                                                                       *
* 186 Bytes total - including GPLLNK, DSRLNK and both Workspaces        *
*-----------------------------------------------------------------------*

PUTSTK EQU  >50                     Push GROM Address to stack pointer
TYPE$  EQU  >836D                   DSRLNK Type byte for GPL DSRLNK
NAMLEN EQU  >8356                   Device name length pointer in VDP PAB
VWA    EQU  >8C02                   VDP Write Address location
VRD    EQU  >8800                   VDP Read Data byte location
G_R4LB EQU  >83E9                   GPL Workspace R4 Lower byte
GSTAT  EQU  >837C                   GPL Status byte location
                                    
*++------------------------------------------------------------------------++*
*++---ADDRESS: >37BE in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
DSRLNK EQU  $LO+$-LLVSPT
       DATA DSRWS,DLINK1            Set BLWP Vectors

*++------------------------------------------------------------------------++*
*++---ADDRESS: >37C2 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
DSRWS  EQU  $LO+$-LLVSPT            Start of DSRLNK workspace
DR3LB  EQU  DSRWS+7                 lower byte of DSRLNK workspace R3
*++------------------------------------------------------------------------++*
*++---ADDRESS: >37C2 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
DLINK1 EQU  $LO+$-LLVSPT
       MOV  R12,R12         R0      Have we already looked up the LINK address?
       JNE  DLINK3          R1      YES!  Skip lookup routine
*<<-------------------------------------------------------------------------->>*
* This section of code is only executed once to find the GROM address          *
* for the GPL DSRLNK - which is placed at DSRADR and R12 is set to >2000       *
* to indicate that the address is found and to be used as a mask for EQ & CND  *
*------------------------------------------------------------------------------*
       LWPI GPLWS           R2,R3   else load GPL workspace
       MOV  @PUTSTK,R4      R4,R5   Store current GROM address on the stack
       BL   *R4             R6
       LI   R4,>11          R7,R8   Load R4 with address of LINK routine vector
       MOVB R4,@>402(R13)   R9,R10  Set up GROM with address for vector

***les*** Note on above instruction:
***les***    1. R13 of GPLWS has >9800=GRMRD (GROM Read Data)
***les***    2. >402 added to GRMRD yields >9C02=GRMWA (GROM Write Address)

       JMP  DLINK2          R11     Jump around R12-R15
       DATA 0               R12     contains >2000 flag when set
       DATA 0,0,0           R13-R15 contains WS, PC & ST for RTWP
DLINK2 MOVB @G_R4LB,@>402(R13)      Finish setting up GROM address
       MOV  @GETSTK,R5              Take some time & set up GETSTK pointer
       MOVB *R13,@DSRAD1            Get the GPL DSR LINK vector
       INCT @DSRADR                 Adjust it to get past GPL FETCH instruction
       BL   *R5                     Restore the GROM address off the stack
       LWPI DSRWS                   Reload DSRLNK workspace
       LI   R12,>2000               Set flag to signify DSRLNK address is set
*<<-------------------------------------------------------------------------->>*
DLINK3 INC  R14                     Adjust R14 to point to caller's DSR Type byte
       MOVB *R14+,@TYPE$            Move it into >836D for GPL DSRLNK
       MOV  @NAMLEN,R3              Save VDP address of Name Length
       AI   R3,-8                   Adjust it to point to PAB Flag byte
       BLWP @GPLLNK                 Execute DSR LINK
*++------------------------------------------------------------------------++*
*++---ADDRESS: >3810 in fbForth 2.0:12-------------------------------------++*
*++------------------------------------------------------------------------++*
DSRADR EQU  $LO+$-LLVSPT
       BYTE >03                     High byte of GPL DSRLNK address
DSRAD1 EQU  $LO+$-LLVSPT
       BYTE >00                     Lower byte of GPL DSRLNK address
*----Error Check & Report to Caller's R0 and EQU bit-------------------------
       MOVB @DR3LB,@VWA             Set up LSB of VDP Address for Error Flag
       MOVB R3,@VWA                 Set up MSB of VDP Address for Error Flag
       SZCB R12,R15                 Clear EQ bit for Error Report
       MOVB @VRD,R3                 Get PAB Error Flag
       SRL  R3,5                    Adjust it to 0-7 error code
       MOVB R3,*R13                 Put it into Caller's R0 (msb)
       JNE  SETEQ                   If it's not zero, set EQ bit
       COC  @GSTAT,R12              Else, test CND bit for Link Error (00)
       JNE  DSREND                  No Error, Just return
SETEQ  SOCB R12,R15                 Error, so set Caller's EQ bit
DSREND RTWP                         All Done - Return to Caller

 

 

...lee

Edited by Lee Stewart
RESOLVED REFERENCES IN CODE SNIPPET...
  • Like 4

Share this post


Link to post
Share on other sites

Just in case anyone is interested, in a few minutes, I will edit the MG code snippet I posted above to resolve references. For fbForth 2.0, that code is copied from ROM to low expansion RAM at startup.

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites
1 hour ago, Lee Stewart said:

Just in case anyone is interested, in a few minutes, I will edit the MG code snippet I posted above to resolve references. For fbForth 2.0, that code is copied from ROM to low expansion RAM at startup.

 

...lee

Thanks Lee! Need to take a look at this, but not now as it is late here where I live.


And thanks to all the other responses by others as well! It is a shame that TI left this half baked on their part. Not that I like GPL, but it is part of what makes the 99/4A what it is, part of it’s character. One more thing to fix if we ever get to building the TI-99/4A Next 🙂

 

Share this post


Link to post
Share on other sites
2 hours ago, mizapf said:

It's because it returns into GPL. It would have been so easy for TI to do it properly, but no.

Thanks. Things like these make this machine hard to understand. Unnecessary one way streets.

Share this post


Link to post
Share on other sites

I have been having fun programming in TMS9900 assembly.

 

I've been thinking about how to demo the strange cart, which somehow evolved into seeing for instance how Parsec works (i.e. the scrolling there). As a result I wrote some miscellaneous code:

- I wrote an assembler routine to convert material on the screen in graphics mode 1 to graphics mode 2. This was a good reminder how these modes work. I wrote the routine since my previous test cartridge code wrote some text on the screen in GM1, and I wanted to use that as test material. This conversion uses the 32K extended memory. It simply first reads VRAM contents to memory extension, and then from that it writes the same stuff back in GM2 memory layout (and it also sets VDP to GM2 of course). At the end the same content appears but in GM2. This was a good reminder that the CPU is not very fast, as it takes surprisingly long, something like a second for this operation to complete.

- I then proceed to wrote some support code to get back to GM1. 

- As part of the GM1 to GM2 conversion I wrote the character indices so that for the top 8 rows it runs linearly from 0..255, which is pretty much the default for GM2. I did the same for the next 8 rows. But for the bottom third of the screen, I used a character layout similar to Parsec: this way the pattern definitions (i.e. contents of character cells) are so that each strip of 64 vertical pixels are in consecutive addresses. So if character x position is a 5 bit number from 0 to 31, and character y position is 3 bit number from 0 to 7 (to be able to address all 256 character cells in the bottom 3rd of the screen), I calculated the character cell values like this: (x << 3) | y. Below is the assembly code:

; Fill the last third with Parsec style scrolling arrangement.
; First column 0, second 8, third 16. Thus the data written is as follows
;  X = 5 bit column value, Y = 3 bit row value
;  Char codes: X4 X3 X2 X1 X0 Y2 Y1 Y0
        CLR  R1
!       MOV  R1,R2
        SLA  R2,3   ; Shift left by three, making bottom three bits of higher byte vacant
        MOV  R1,R3
        SRL  R3,5   ; Shift right by 5, move top 3 bits to three lsbs of higher byte
        SOC  R3,R2  ; set ones corresponding, i.e. OR 
        MOVB R2,@VDPWD
        AI   R1,>0100
        JNE  -!

- I then went on to test Parsec style scrolling routines. My code was in cartridge ROM area, workspace in scratchpad memory. I set a side a Parsec style buffer of 128 bytes for 2 strips of 8 pixels wide times 64 pixels tall. Then, I read two columns into this buffer, so that bytes from first and second column alternate. With this arrangement the actual scrolling goes the same way as in Parsec (although in parsec source code the shift instruction on line 3050 of the PDF I found is SRC R1,0 which makes no sense to me at first glance). 

*---------------------------------------------
* Write a strip of 64 pixels while scrolling.
* Parsec style. VDP write pointer must have been set.
SCROLLSTRIP:
    mov @STRIPBU2(R5),r1    ; read first and second column bytes
    sla r1,1                ; shift left by 1 bit (i.e. pixel)
    movb r1,@>8c00          ; write to VRAM
    inct R5                 ; inc R5 by 2 (i.e. skip the other column)
    jlt SCROLLSTRIP                  ; loop back
    RT

This turned out to be quite slow for the entire 64 pixel high area, far away from instantenous. 

My first attempt was to use an algorithm which on every loop iteration reads two strips, and then writes and shifts one strip, repeated 32 times for every column. This is kind of how parsec works, except it only scrolls a small horizontal section at a time, and the height is 30 pixel rows.

My scroll was not going fast. I then modified the SCROLLSTRIP routine above to perform byteswapping and storing the swapped bytes, that way I only needed to read one strip and write one strip, except on first iteration. This was a bit better but still slow. 

As the last test I limited my code to only scroll 16 scanlines, and moved the "strip buffer" to scratchpad memory (my workspace is at >8300 and the buffer, now just 32 bytes, is at >8320). It seems like scrolling 16 vertical lines of all 256 pixels takes about 10 seconds, or goes at about 25fps. I could copy the code to scratchpad memory too, but did not bother at this point.

 

This was a pretty interesting exercise, as a lead in to what would happen if I used the faster processor on the cartridge to compute the scrolls, and have the TMS9900 just copy the data to VDP memory. Or something like that. 

 

Overall it was fun to do some assembly programming after a while. One question which came to mind was that how would one easily do cycle counting, does classic99 for example support it? I.e. reset cycle counter at a break point, and read the count at another breakpoint?

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, speccery said:

 

Overall it was fun to do some assembly programming after a while. One question which came to mind was that how would one easily do cycle counting, does classic99 for example support it? I.e. reset cycle counter at a break point, and read the count at another breakpoint?

Just add a "breakpoint" like T(start-end), e.g. T(A000-A030) to count the cycles between >a000 and >a030.

  • Like 3

Share this post


Link to post
Share on other sites
25 minutes ago, Asmusr said:

Just add a "breakpoint" like T(start-end), e.g. T(A000-A030) to count the cycles between >a000 and >a030.

Nice, thanks for the tip!

  • Like 1

Share this post


Link to post
Share on other sites
6 hours ago, speccery said:

With this arrangement the actual scrolling goes the same way as in Parsec (although in parsec source code the shift instruction on line 3050 of the PDF I found is SRC R1,0 which makes no sense to me at first glance). 

 

You say “at first glance”, which could mean you figured it out, but, just in case you did not, “SRC R1,0” means to shift R1 by the number of bits found in the low-order 4 bits of R0. If they are 0, then 16 bits are shifted.

 

...lee

Share this post


Link to post
Share on other sites
13 hours ago, Lee Stewart said:

 

You say “at first glance”, which could mean you figured it out, but, just in case you did not, “SRC R1,0” means to shift R1 by the number of bits found in the low-order 4 bits of R0. If they are 0, then 16 bits are shifted.

 

...lee

Thanks Lee, good comment. A quick breakpoint in Parsec loads R0 with 6, so a shift right of 6. Which I suppose in practice means a shift of 2 pixels to the left, if the bytes are organised in memory the way I think. I did not pay too much attention to this instruction not matching my expectation, as I was more focused on getting the framework in place. I have now done that.

Share this post


Link to post
Share on other sites
On 5/31/2020 at 9:14 PM, speccery said:

Nice, thanks for the tip!

Thanks to the tip from @Asmusr I used classic99 to measure the speed of my code. With some simple changes I was able to get the time of a one bit left shift for 16 scanlines down from around 135K cycles to about 97K cycles. Only modifying the pattern tables, not touching color tables. There are in total 32*16=512 VDP RAM reads and and the same amount of writes. Come to think go it, my code actually probably reads one extra strip at the end. Anyway, in the big picture that's around 100 cycles per each VDP access and processing.

I still find it mind boggling that instructions such as "MOVB *R7,@STRIPBUF" take 38 clock cycles (over 10 microseconds) when running from cartridge ROM space. In this example R7 points to a VDP register, workspace and STRIPBUF are in scratchpad. Of course there are 4 opcode bytes to fetch, and the store is a read-modify-write cycle, so there are quite a few memory cycles involved in the execution of that instruction. If I did my math properly there are 6 memory cycles in total, each 16-bits. Since two of these are to ROM memory and broken up with wait states to four byte wide fetches, the total is in a way 8 memory cycles.

This actually makes me wonder what would be the slowest MOV instruction one could have? I suppose it would mean placing code and operands into 8-bit wide memory, and then perhaps accessing GROM to get the maximum penalty...

Share this post


Link to post
Share on other sites
Posted (edited)
4 hours ago, speccery said:

This actually makes me wonder what would be the slowest MOV instruction one could have? I suppose it would mean placing code and operands into 8-bit wide memory, and then perhaps accessing GROM to get the maximum penalty...

The speech ROM is slower for reads, and the sound chip is slower for writes. (Although I can't remember the exact speed of speech ROM, I am pretty sure Classic99 doesn't emulate it.. but I had notes somewhere). But I'd expect a mov from speech to sound would do it. ;)

 

(Edit: oh, it might! I have it in my measured docs. Reading speech ROM takes 54 cycles, but write speech was 70! So I guess MOV from speech to speech, and you should be looking at about 150 cycles?)

 

Edited by Tursi
  • Like 1

Share this post


Link to post
Share on other sites
On 5/27/2020 at 2:53 PM, Lee Stewart said:

Just in case anyone is interested, in a few minutes, I will edit the MG code snippet I posted above to resolve references. For fbForth 2.0, that code is copied from ROM to low expansion RAM at startup.

 

...lee

Interested party here :) 

 

This is a really nice piece of code which I was not able to understand back when I started trying to make a DSRLNK.

The fog has lifted a little. I may attempt to "borrow" another fine piece of FbForth in future because this version is small and has more functionality than what I currently use.

I think I can see my way to translating it into Forth Assembler because my cross-compiler assembler has labelled jumps.

 

Questions:

1. Does this code assume the presence of and/or use any system routines in low RAM?

2. Since you copy from ROM to RAM is it safe to say that I should be able to leave it in place under Camel99 Forth because I am loading the kernel into RAM?

 

Share this post


Link to post
Share on other sites
2 hours ago, TheBF said:

Interested party here :) 

 

This is a really nice piece of code which I was not able to understand back when I started trying to make a DSRLNK.

The fog has lifted a little. I may attempt to "borrow" another fine piece of FbForth in future because this version is small and has more functionality than what I currently use.

I think I can see my way to translating it into Forth Assembler because my cross-compiler assembler has labelled jumps.

 

Questions:

1. Does this code assume the presence of and/or use any system routines in low RAM?

2. Since you copy from ROM to RAM is it safe to say that I should be able to leave it in place under Camel99 Forth because I am loading the kernel into RAM?

 

 

The code I slightly modified is from MG’s The Smart Programmer, V2(2), July, 1986. Here is my transcription of it:

Spoiler
*--------------------------------------------------------------------*
* GPLLNK- A universal GPLLNK - 6/21/85 - MG                          *
* This routine will work with any GROM library slot since it is      *
* indexed off of R13 in the GPLWS.  (It does require Mem Expansion)  *
* This GPLLNK does NOT require a module to be plugged into the       *
* GROM port so it will work with the Editor/Assembler,               *
* Mini Memory (with Mem Expansion), Extended Basic, the Myarc        *
* CALL LR("DSKx.xxx") or the CorComp Disk Manager Loaders.           *
* It saves and restores the current GROM Address in case you want    *
* to return back to GROM for Basic or Extended Basic CALL LINKs      *
* or to return to the loading module.                                *
*                                                                    *
*    ENTER: The same way as the E/A GPLLNK, i.e., BLWP @GPLLNK       *
*                                                 DATA >34           *
*                                                                    *
*    NOTES: Do Not REF GPLLNK when using this routine in your code.  *
*                                                                    *
* 70 Bytes - including the GPLLNK Workspace                          *
*--------------------------------------------------------------------*

GPLWS  EQU  >83E0                GPL workspace
GR4    EQU  GPLWS+8              GPL workspace R4
GR6    EQU  GPLWS+12             GPL workspace R6
STKPNT EQU  >8373                GPL Stack pointer
LDGADD EQU  >60                  Load & ECUTE GROM address entry point
XTAB27 EQU  >200E                Low Mem XML table location 27
GETSTK EQU  >166C

GPLLNK DATA GLNKWS      R7       Set up BLWP Vectors
       DATA GLINK1      R8

RTNAD  DATA XMLRTN      R9       address where GPL XML returns to us
GXMLAD DATA >176C       R10      GROM Address for GPL XML (0F 27 Opcode)
       DATA >50         R11      Initialized to >50 where PUTSTK address resides
GLNKWS EQU  $->18            GPLLNK's workspace of which only
       BSS  >08         R12-R15  registers R7 through R15 are used

GLINK1 MOV  *R11,@GR4            Put PUTSTK Address into R4 of GPL WS
       MOV  *R14+,@GR6           Put GPL Routine Address in R6 of GPL WS
       MOV  @XTAB27,R12          Save the value at >200E
       MOV  R9,@XTAB27           Put XMLRTN Address into >200E
       LWPI GPLWS                Load GPL WS
       BL   *R4                  Save current GROM Address on stack
       MOV  @GXMLAD,@>8302(R4)   Push GPL XML Add on stack for GPL ReTurn
       INCT @STKPNT              Adjust the stack pointer
       B    @LDGADD              Execute our GPL Routine
                                 
XMLRTN MOV  @GETSTK,R4           Get GETSTK pointer
       BL   *R4                  Restore GROM address off the stack
       LWPI GLNKWS               Load our WS
       MOV  R12,@XTAB27          Restore >200E
       RTWP                      All Done - Return to Caller

*--------------------------------------------------------------------*
* DSRLNK - A Universal Device Service Routine Link - MG              *
*      (Uses console GROM 0's DSRLNK routine)                        *
*      (Do not REF DSRLNK or GPLLNK when using these routines)       *
*      (This DSRLNK will also handle Subprograms and CS1, CS2)       *
*                                                                    *
*      ENTER: The same way as the E/A DSRLNK, i.e., BLWP @DSRLNK     *
*                                                   DATA 8           *
*                                                                    *
*      NOTES: Must be used with a GPLLNK routine                     *
*             Returns ERRORs the same as the E/A DSRLNK              *
*             EQ bit set on return if error                          *
*             ERROR CODE in caller's MSB of Register 0 on return     *
*                                                                    *
* 186 Bytes total - including GPLLNK, DSRLNK and both Workspaces     *
*--------------------------------------------------------------------*

PUTSTK EQU  >50                    Push GROM Add to stack pointer
TYPE   EQU  >836D                  DSRLNK Type byte for GPL DSRLNK
NAMLEN EQU  >8356                  Device name length pointer in VDP PAB
VWA    EQU  >8C02                  VDP Write Address location
VRD    EQU  >8800                  VDP Read Data byte location
GR4LB  EQU  >83E9                  GPL Workspace R4 Lower byte
GSTAT  EQU  >837C                  GPL Status byte location
                                 
DSRLNK DATA DSRWS,DLINK1           Set BLWP Vectors
                                 
DSRWS  EQU  $                      Start of DSRLNK workspace
DR3LB  EQU  $+7                    R3 lower byte of DSRLNK workspace
DLINK1 MOV  R12,R12        R0      Have we already looked up the LINK address?
       JNE  DLINK3         R1      YES!  Skip lookup routine
*<<-------------------------------------------------------------------------->>*
* This section of code is only executed once to find the GROM address          *
* for the GPL DSRNK - which is placed at DSRADD and R12 is set to >2000        *
* to indicate that the address is found and to be used as a mask for EQ & CND  *
*------------------------------------------------------------------------------*
       LWPI GPLWS          R2,R3   else load GPL workspace
       MOV  @PUTSTK,R4     R4,R5   Store current GROM address on the stack
       BL   *R4            R6
       LI   R4,>11         R7,R8   Load R4 with address of LINK routine vector
       MOVB R4,@>402(R13)  R9,R10  Set up GROM with address for vector
***les***
***les*** Note on above instruction:
***les***    1. R13 of GPLWS has 9800h=GRMRD (GROM Read Data)
***les***    2. 402h added to GRMRD yields 9C02h=GRMWA (GROM Write Address)
***les***
       JMP  DLINK2         R11     Jump around R12-R15
       DATA 0              R12     contains >2000 flag when set
       DATA 0,0,0          R13-R15 contains WS, PC & ST for RTWP
DLINK2 MOVB @GR4LB,@>402(R13)      Finish setting up GROM address
       MOV  @GETSTK,R5             Take some time & set up GETSTK pointer
       MOVB *R13,@DSRAD1           Get the GPL DSR LINK vector
       INCT @DSRADD                Adjust it to get past GPL FETCH instruction
       BL   *R5                    Restore the GROM address off the stack
       LWPI DSRWS                  Reload DSRLNK workspace
       LI   R12,>2000              Set flag to signify DSRLNK address is set
*<<-------------------------------------------------------------------------->>*
DLINK3 INC  R14                    Adjust R14 to point to caller's DSR Type byte
       MOVB *R14+,@TYPE            Move it into >836D for GPL DSRLNK
       MOV  @NAMLEN,R3             Save VDP address of Name Length
       AI   R3,-8                  Adjust it to point to PAB Flag byte
       BLWP @GPLLNK                Execute DSR LINK
DSRADD BYTE >03                    High byte of GPL DSRLNK address
DSRAD1 BYTE >00                    Lower byte of GPL DSRLNK address
*------Error Check & Report to Caller's R0 and EQU bit-------------------------
       MOVB @DR3LB,@VWA            Set up LSB of VDP Add for Error Flag
       MOVB R3,@VWA                Set up MSB of VDP Add for Error Flag
       SZCB R12,R15                Clear EQ bit for Error Report
       MOVB @VRD,R3                Get PAB Error Flag
       SRL  R3,5                   Adjust it to 0-7 error code
       MOVB R3,*R13                Put it into Caller's R0 (msb)
       JNE  SETEQ                  If it's not zero, set EQ bit
       COC  @GSTAT,R12             Else, test CND bit for Link Error (00)
       JNE  DSREND                 No Error; Just return
SETEQ  SOCB R12,R15                Error, so set Caller's EQ bit
DSREND RTWP                        All Done - Return to Caller

 

 

Re question (1), the code does not assume the presence of and/or use any system routines in low RAM. However, you cannot use the address, >200E (XTAB27), for anything else. Craig Miller found the only :o occurrence in console GROM 0 of code that could be used as the GPL XML instruction (>0F27) that would execute a return to RAM from GPL. >0F is the machine code for the GPL XML instruction and >27 decodes to address >200E, which will hold the RAM return address (XMLRTN) to the GSRLNK/DSRLNK code in RAM.

 

Re question (2), except for location >200E mentioned above, your Camel99 Forth RAM code can safely be left wherever your Assembler puts it in RAM.

 

...lee

  • Like 3

Share this post


Link to post
Share on other sites
26 minutes ago, Lee Stewart said:

 Craig Miller found the only :o occurrence in console GROM 0 of code that could be used as the GPL XML instruction (>0F27) that would execute a return to RAM from GPL. >0F is the machine code for the GPL XML instruction and >27 decodes to address >200E, which will hold the RAM return address (XMLRTN) to the GSRLNK/DSRLNK code in RAM.

Wow that is some good cyber-sleuthing.

For some reason I am thinking of a guy named Job  ( not Jobs :)  )

 

I will have to move my Heap pointer forward a little but it is workable.

 

Thanks for the great info as always.

Share this post


Link to post
Share on other sites
On 5/27/2020 at 8:21 AM, Asmusr said:

The thing with GPL DSRs is that they only work from BASIC because assembly programs generally don't look for them.

INCORRECT!

TI Basic, XB, Basic Support Module, TEII, Editor Assembler, TI Writer and Mini Memory plus many others all support GPL DSR.

Share this post


Link to post
Share on other sites
5 hours ago, Tursi said:

The speech ROM is slower for reads, and the sound chip is slower for writes. (Although I can't remember the exact speed of speech ROM, I am pretty sure Classic99 doesn't emulate it.. but I had notes somewhere). But I'd expect a mov from speech to sound would do it. ;)

 

(Edit: oh, it might! I have it in my measured docs. Reading speech ROM takes 54 cycles, but write speech was 70! So I guess MOV from speech to speech, and you should be looking at about 150 cycles?)

 

Wow that is a lot! The best I could do (on a very quick attempt, and I don't have the speech module here in my temporary apartment) was 77 cycles, with MOV @>9800(R1),@>A000(R1). Measured with your fantastic classic99.

BTW: is there a way to save / restore breakpoints? I think you already answered to me in some message before, but can I reload a cartridge image (during development) without exiting classic?

Share this post


Link to post
Share on other sites
9 hours ago, speccery said:

Thanks to the tip from @Asmusr I used classic99 to measure the speed of my code. With some simple changes I was able to get the time of a one bit left shift for 16 scanlines down from around 135K cycles to about 97K cycles. Only modifying the pattern tables, not touching color tables. There are in total 32*16=512 VDP RAM reads and and the same amount of writes. Come to think go it, my code actually probably reads one extra strip at the end. Anyway, in the big picture that's around 100 cycles per each VDP access and processing.

If you want to speed things up you should look into how to avoid any VDP reads and how to avoid settings up the VDP write address more than once. 

  • Like 1

Share this post


Link to post
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.

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