Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

1 hour ago, PeteE said:

To use the scratchpad for data, I use a group of EQUates to define address of variables, leaving room for the register workspace, and calculate the offsets manually by adding the size of the previous item to the current offset:


WRKSP EQU >8300    ; workspace at top of scratchpad
PLAYER EQU WRKSP+32  ; 32 is the size of WRKSP, player is 2 bytes
ENEMY EQU WRKSP+34   ; 2 bytes after PLAYER, enemy is 2 bytes
KEYLOC EQU WRKSP+36   ; 2 bytes after ENEMY, keyloc is 2 bytes
ETC

Keep in mind the scratchpad is only 256 bytes, so the offset cannot exceed 255... and you either need to avoid using scratchpad data used by the ISR, or keep  interrupts off (LIMI 0) for the duration of your program.

   Thanks @PeteE. I’m familiar with the scratchpad and EQUates. I do like the way you’re using offsets vs actual addresses, as I’m using.

 

   Your method makes it easy to maintain available scratchpad awareness without doing any Hex math. 
 

   As for the ISR, I’ve been convinced to avoid that for game programming with LIMI 0 as per previous posts on this same thread. Rolling my own routines to preserve scratchpad predictability and speed.  

  • Like 1
Link to comment
Share on other sites

50 minutes ago, Airshack said:

   Thanks @PeteE. I’m familiar with the scratchpad and EQUates. I do like the way you’re using offsets vs actual addresses, as I’m using.

 

   Your method makes it easy to maintain available scratchpad awareness without doing any Hex math. 
 

   As for the ISR, I’ve been convinced to avoid that for game programming with LIMI 0 as per previous posts on this same thread. Rolling my own routines to preserve scratchpad predictability and speed.  

Another method I've been meaning to try out is the xdt99 feature of XORG, so you could lay out your data in memory using DATA statements without having to calculate offsets manually, like this:

VARDAT XORG >8320      ; start after workspace
PLAYER DATA >0000
ENEMY  DATA >0000
KEYLOC DATA >0000
ETC

The difference between this and AORG, is that the DATA bytes are copied into your program at VARDAT, so you can initialize your variables by copying words from VARDAT to >8320 when your program starts.

  • Thanks 1
Link to comment
Share on other sites

11 hours ago, PeteE said:

In order to run on an unexpanded console, you need to put your program in cartridge ROM like this:


       AORG >6000      ; start at cartridge ROM

* cartridge header
* your program code

MAPDAT
       BYTE ....  ; your map data from Magellan
       BYTE ....  ; note this is now read-only

This is interesting. Let’s say I’d like to test what I have so far using FinalGROM. After using AORG >6000, I’ll need to know more about cartridge headers.

 

Any good sources? I have but a vague notion of what a cartridge header involves. 

Link to comment
Share on other sites

41 minutes ago, Airshack said:

This is interesting. Let’s say I’d like to test what I have so far using FinalGROM. After using AORG >6000, I’ll need to know more about cartridge headers.

 

Any good sources? I have but a vague notion of what a cartridge header involves. 

Here's an example cartridge header, with a single program in the program list:

       AORG >6000         ; Cartridge header in all banks

HEADER
       BYTE >AA     ; Standard header
       BYTE >01     ; Version number 1
       BYTE >01     ; Number of programs (optional)
       BYTE >00     ; Reserved (for FG99 this can be G,R,or X)
       DATA >0000   ; Pointer to power-up list
       DATA PRGLST  ; Pointer to program list
       DATA >0000   ; Pointer to DSR list
       DATA >0000   ; Pointer to subprogram list

PRGLST DATA >0000   ; Next program list entry
       DATA START   ; Program address
       BYTE CRTNME-CRTNM       ; Length of name
CRTNM  TEXT 'CARTRIDGE NAME'
CRTNME
       EVEN

START                         ; Your program starts here
       LWPI WRKSP             ; Load the workspace pointer to fast RAM
       LIMI 0                 ; Interrupts off
       ...

You would change the string at CRTNM to reflect what you want to appear in the TI menu "PRESS 2 FOR" ... and it must appear in all upper-case.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

22 hours ago, Airshack said:

 One thought: If I keep track of where my key objects should be located on the scrollable game map, I can simply plot them over grass tiles, after each viewport redraw. I’m imagining this will create a blinking appearance as the map is continually redrawn, and the key is then replotted. Perhaps it will appear as a nice feature highlighting the key’s location?

If you, say, "require" the Memory Expansion (32K) for your game, you can copy the map from ROM (cartridge) to CPU RAM (and go from there as you wanted). Lots of bigger games of later years require the Memory Exp., so that's all okay, except if you're a wannabe purist. It's probably my kinda niche, though I'll keep doors open. Back in the day, millions or most TI-99/4A owners did not have the Memory Exp. These days, anyone with emulation has easy (or de-facto) access to the Memory Exp.
 
Also (with or without Memory Exp.), you can have more than one screen in VDP RAM. You can draw on one, while displaying another. Often refered to as double buffering. When you've done drawing, you switch the display to that new location. You can theoretically (and practical) have as much as 16 different screens stored in the VDP at once. If you switch right after a VDP interrupt, you'll see no screen tearing on real hardware (have to assume unexpanded console because of interrupts) (also Classic99 generally tears a lot these days, but that's another issue).

 

Edited by sometimes99er
  • Like 3
  • Thanks 1
Link to comment
Share on other sites

21 hours ago, PeteE said:

Here's an example cartridge header, with a single program in the program list:


       AORG >6000         ; Cartridge header in all banks

HEADER
       BYTE >AA     ; Standard header
       BYTE >01     ; Version number 1
       BYTE >01     ; Number of programs (optional)
       BYTE >00     ; Reserved (for FG99 this can be G,R,or X)
       DATA >0000   ; Pointer to power-up list
       DATA PRGLST  ; Pointer to program list
       DATA >0000   ; Pointer to DSR list
       DATA >0000   ; Pointer to subprogram list

PRGLST DATA >0000   ; Next program list entry
       DATA START   ; Program address
       BYTE CRTNME-CRTNM       ; Length of name
CRTNM  TEXT 'CARTRIDGE NAME'
CRTNME
       EVEN

START                         ; Your program starts here
       LWPI WRKSP             ; Load the workspace pointer to fast RAM
       LIMI 0                 ; Interrupts off
       ...

You would change the string at CRTNM to reflect what you want to appear in the TI menu "PRESS 2 FOR" ... and it must appear in all upper-case.

Hmmm... with this tidy information I can see a Cartridge version of CAMEL99 Forth coming out of the cross-compiler. :) 

This makes it look very straightforward.

 

Thanks very much for this.

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

4 minutes ago, TheBF said:

Hmmm... with this tidy information I can see a Cartridge version of CAMEL99 Forth coming out of the cross-compiler. :) 

This makes it look very straightforward.

 

Thanks very much for this.

 

Same kind of info at the head of each of the four banks of fbForth 2.0 source code available on my website below.

 

...lee

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

1 minute ago, Lee Stewart said:

 

Same kind of info at the head of each of the four banks of fbForth 2.0 source code available on my website below.

 

...lee

Awesome. Thanks Lee.

I have never gone down that path but it was on my todo list. The post by PeteE made me see that it's not as tricky as I imagined.

Away on some business for the weekend but will get back to the coding next week early. 

Freeing up 8K more Expansion RAM would really make a difference for the size of project I can support.

I have been envious of you and Willsy on that front. :) 

 

 

  • Thanks 1
Link to comment
Share on other sites

43 minutes ago, TheBF said:

Hmmm... with this tidy information I can see a Cartridge version of CAMEL99 Forth coming out of the cross-compiler. :) 

This makes it look very straightforward.

 

Thanks very much for this.

 

You're welcome. I can't claim credit though, I'm sure I took it from somewhere else, but I can't remember where.  Probably here

Edited by PeteE
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

On 1/23/2020 at 2:44 PM, Airshack said:

   Anyway, it’s working now. I have a bridge key on my game map! 

 

   MOV    @MAPDAT, R6     * pointer to beginning of map

   LI        R7,>D900          * Character D9 = bridge key

   MOVB  R7,*R6               * Place key at map origin as a test

 

   My problem last night was I was using the following first line:

 

    LI        R6, @MAPDAT        * then basically corrupting MAPDAT                                         

 

   Caused all sorts of havoc onto my game screen.

Well, none of the above is 100% correct.

 

MOV @MAPDAT,R6 will not store a pointer to MAPDAT in R6, but the content of the memory word at MAPDAT. Thus your byte >D9 will end up at the address pointed to by the data at the first word of MAPDAT, not in the first word of MAPDAT. This will defnitely not be what you expect, and will perhaps lock up your computer. That's the least myserious thing that could occur.

 

On the other hand, LI R6,@MAPDAT isn't correct either. It's not even correct syntax, since such a construct doesn't exits for the TMS 9900. What you want to do is LI R6,MAPDAT, since it's the value of MAPDAT you want to use as a pointer to the first MAPDAT word.

 

To map an area in memory, that's not in line with your code, you can use a construct even TI's original assembler provides. You can use dummy segments, where the dummy origin, DORG, allows for generating entries in the symbol list, without generating any code.

 

DORG >8300

FIRST DATA 1234

SECOND DATA >35F2

THIRD BYTE 22

FOURTH TEXT 'X'

 

Will generate symbol table entries for the labels, like if they were assembled starting at address >8300. But no data is created in a dummy segment, nor loaded into memory at load time.

Edited by apersson850
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

  • 2 months later...

I think this question has been answered somewhere in Atariage but I can't find the right search terms to find it 

 

Is it possible to run a program with E/A 5 and return to the E/A cartridge menu?  (versus exiting to the main screen)

 

Link to comment
Share on other sites

11 minutes ago, HOME AUTOMATION said:

(maybe)...
E/A manual, 24.11.3 p.442

 

Other returns

 

Clear the GPL STATUS byte,
load GPL Workspace Registers,
branch to @>0070.

 

   P.S. In another example(speech) they show >006A as the return addr(p.361).

 

   P.P.S. I used to be more familiar with these.:roll:

 

Thanks!.   I saw that but had no idea if I was supposed to go back to the GPL interpreter. :)

I will give it a try.

\ SIMPLE super cart binary program
HEX
CROSS-ASSEMBLING
              START.
              NEW.
              ABSOLUTE 6000 ORIGIN.    \ super cart RAM >6000
              TI-99.EA5                \ builds the file header in RAM
\ ===============================================
 PROGRAM: GPLRTN
             8300 LWPI,         \ set workspace pointer located at >8300, fast ram
\ wasting time loop
             R1 FFFF LI,
             BEGIN,
               E002 @@ INCT,
               E004 @@ DEC,
               R1 DEC,
             EQ UNTIL,
\ * return to GPL with ROM routine
             R0 CLR,
             R0 837C @@ MOVB,
             83E0 LWPI,
             0070 @@ B,
             END.
\ ==============================================
\ save the binary image file
             FILENAME$ $SAVE-EA5. 

             BYE

And hey it works. 

This code is pretty weird cuz it's written for my Forth cross-assembler but you get the idea.

 

 

  • Like 3
Link to comment
Share on other sites

10 minutes ago, mizapf said:

006A clears the GPL status (see TI Intern). You should also make sure that the GROM address counter did not change (no accesses to GROM while running the machine language program).

This works as well without clearing >837C first.

 

Thanks everyone.

 

 

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...
1 hour ago, mizapf said:

You don't mean MOV Rx,Rx, I suppose.

 

Maybe SRL Rx,15?

 

You cannot test for 0 and for the signs at the same time, I think.

I want the result 1, 0 or -1 in the register.

 

If you SRA Rx,15 that gives you 0 or -1, but how do you get 1 if Rx > 0?

  • Like 1
Link to comment
Share on other sites

4 hours ago, Asmusr said:

I want the result 1, 0 or -1 in the register.

 

If you SRA Rx,15 that gives you 0 or -1, but how do you get 1 if Rx > 0?

You can get 1 by doing ((abs(r)^-r)>>15)  which is basically "if the sign bit changes between negating and absolute value, then you know it's > 0"

* Returns 1 if R0>0, -1 if R0<0, otherwise 0
       MOV R0,R1
       MOV R0,R2
       NEG R1
       ABS R2
       XOR R2,R1
       SRL R1,15
       SRA R0,15
       SOC R1,R0

This even works for -32768 which would usually be an edge case.

 

EDIT: What is your goal for the exercise?  No branches, fewest instructions, shortest execution time?

 

Edited by PeteE
  • Like 1
Link to comment
Share on other sites

3 hours ago, Asmusr said:

I want the result 1, 0 or -1 in the register.

 

If you SRA Rx,15 that gives you 0 or -1, but how do you get 1 if Rx > 0?

 

Sample value in R0, result in R1.

 

  LI  R1,1
  MOV R0,R0
  JGT  DONE
  SRA  R0,15
DONE ...

 

Edited by mizapf
Space lines in code
  • Like 2
Link to comment
Share on other sites

1 hour ago, mizapf said:

 

Sample value in R0, result in R1.

 


  LI  R1,1
  MOV R0,R0
  JGT  DONE
  SRA  R0,15
DONE ...

 

 

Don't you need to move R0 to R1 after the SRA?

 

The SRA will take 2 cycles for every position shifted, plus 12 cycles, plus 3 mem cycles.  That makes it pretty expensive.  (42 + 3*mem).  If I add up the worst case for your routine (adding the missing MOV):

  LI   R1, 1    ; 12 + 3*mem
  MOV  R0, R0   ; 14 + 4*mem
  JGT  DONE     ;  8 + 1*mem (not taken)
  SRA  R0, 15   ; 42 + 3*mem
  MOV  R0, R1   ; 14 + 4*mem
DONE ...        ;------------
                ; 90 + 15*mem

 

 

How about this one, which leaves the result in the same register it started with?

     NEG   R0      ; 12 + 3*mem        Sets EQ if zero, GT if negative
     SETO  R0      ; 10 + 3*mem        R0 = -1, no flag modifications
     JGT   DONE    ;  8 + 1*mem (n/t)  If R0 was negative, we're done.
     JNE   POS     ;  8 + 1*mem (n/t)  If R0 was non-zero, turn -1 to +1
     DEC   R0      ; 10 + 3*mem        R0 was 0, so turn -1 to -2, to become 0
POS: INCT  R0      ; 10 + 3*mem        This either gives is 0 or +1 now.
DONE:              ;-----------
                   ; 58 + 14*mem
   

I think this ends up being the same size, too, once you add the missing MOV to yours.

 

(FWIW, I'm using the cycle counts here, and the instruction references here and here.)

 

I know the original request was without tests or jumps:

7 hours ago, Asmusr said:

Is there a clever way in assembly - without tests and jumps - to calculate the sign of a register (if r > 0 then 1 else if r < 0 then -1 else 0)? 

But on this architecture, jumps are surprisingly cheap compared to the alternatives.  They're cheaper than MOVs, which is surprising until you remember this is a memory-memory architecture.

Edited by intvnut
Further clarification on cost structure of ISA. Add comments to my code.
  • Like 1
Link to comment
Share on other sites

7 minutes ago, Lee Stewart said:

Considering the necessity of jumps, I see this as shortest:


       MOV  R0,R0
       JEQ  DONE
       SETO R0
       JLT  DONE
       INCT R0
DONE   ...

 

...lee

 

*doh*  Of course!  You can use the fact R0 is 0 to save an instruction over my approach, and be slightly faster both on average and in the worst case. 

       MOV  R0,R0  ; 14 + 4*mem
       JEQ  DONE   ;  8 + 1*mem (n/t)
       SETO R0     ; 10 + 3*mem
       JLT  DONE   ;  8 + 1*mem (n/t)
       INCT R0     ; 10 + 3*mem
DONE   ...         ;------------
                   ; 50 + 12*mem

50+12*mem is noticeably faster than my 58+14*mem.

 

It's annoying MOV is 14 + 4*mem.  Does ABS set Carry in a useful way for positive vs. negative numbers?  It's faster than MOV for positive numbers, and uses fewer memory cycles in all cases.

Edited by intvnut
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...