Jump to content
IGNORED

Tips for mixing assembly with XB


retrodroid
 Share

Recommended Posts

On 12/21/2022 at 1:28 PM, retrodroid said:

I noticed that the Magellan -> Export -> Assembly Data -> RLE Compress Maps only supports up char 127.  Can someone share why that is?

 

I wrote a little RLE byte compressor to see how my map data would benefit and it reduces my maps to anywhere from 60% of the original size to only 20% of original size, a huge savings.

Ideally, I'd like to leverage the wonderful Magellan tool fully and have it produce my RLE compressed map data, but ran into this limitation.

 

To answer my own question, I had a look at the Magellan source code and it's implementing a code-type RLE scheme which uses the MSB of a character byte to indicate a repeating sequence. That is why only 128 chars can be used.  

// RLE compression (byte)
// We assume all characters are < 128. If msb is set, the next byte determines
// how many times (2 - 256) the current byte (with msb cleared) should be repeated.
// A repeat count of 0 is used as end marker.

 

This is efficient since it doesn't require a dedicated "code" byte, thus, a repeating sequence of chars can be identified with only 2 bytes, one for the char to repeat (plus the MSB flag), and one to identify how many to repeat.  However, I need to use 256 chars in my project so will implement my own RLE scheme.  I aspire to provide this as an alternate RLE option back to the Magellan project in the future.

 

I haven't implemented this yet, if anyone sees any potential "gotchas" with this approach please point them out.  

 

Apologies for the "mixed" ascii / Hex notation used below, thought it made visualizing it easier.

 

Custom "Flag" type RLE Encoder to support up to 255 distinct chars, and unlimited number of char repeats.

Dedicates the 255/FF character as the character repeat flag.
* As a marker that a repeat of the next char value should be made
* As an indicator that the char should be repeated 255 times + the next byte value (allows chaining of multiple repeat value bytes)

Examples

// RLE only applies if a char repeats > 3 times, otherwise no space savings to be had. 
Input:  t e s t t e r
Output: t e s t t e r

Input:  t e s t t t e r
Output: t e s t t t e r
 
Input:  t e s t t t t e r
Output: t e s FFt04 e r

// "c" repeats 210 times
Input:  a b c*210 d e f
Output: a b FFcD2 d e f

// "c" repeats 255 times
Input:  a b c*255 d e f
Output: a b FFcFF00 d e f

// "c" repeats 256 times
Input:  a b c*256 d e f
Output: a b FFcFF01 d e f

// "c" repeats 550 times
Input:  a b c*550 d e f
Output: a b FFcFFFF28 d e f

// char(255) used as both a valid char value & the repeat/chaining flag...
// This way we don't lose the use of the 255 char.
Input:  a b char(255) c d
Output: a b FFFF01 c d 

// char(255) repeats 10 times
Input:  a b char(255)*10 c d
Output: a b FFFF0A c d 

 

Edited by retrodroid
  • Like 2
Link to comment
Share on other sites

Well, it seems the Subject of this thread isn't really relevant to my current plans for this project. Should I change it and use this as my project thread, or start a new one?

 

So I think I have a handle on how much memory I'll need to store the levels in my platform game. I'll be storing a template version of each unique screen type, without any decorations at all (ladders, monsters, treasures, etc.). Each level of the game will use a map featuring a combination of these screen templates + a unique set of decorations per screen instance. That way as the player progresses to higher levels, they can encounter different / more difficult combinations of obstacles, etc. This approach of separating the basic screen layout from the "stuff"/decorations also has the advantage of avoiding a lot of the small unique (non repeating) character usage, and vertically repeating chars like ladders, both of which can add a lot of bulk when using the RLE compression scheme, since they tend to break up what would otherwise be larger sections of repeating chars.

 

I'm also working through the data structures for the various decorations. It looks like I need 2.5 bytes per item in terms of static information required to place the object on the screen.  Not sure if there's an easy way to use 2.5 bytes vs 3, so call it 3?  Of course, I'll need more bytes for each object to track the runtime state for each while they are in-use on the currently displayed screen.

 

I ran some calculations/simulations to see how much memory was going to be required to store the maps (screen templates + decorations). The maps range from simple (hallway with a ladder) to middling, to complex, in terms of structure complexity and number of objects.  As shown below, the difference between no compression, RLE compression with all decorations included/drawn on the screen, and "Smart" RLE compression, with the decorations removed from the screen and stored in their own data-structure (3 bytes each) is remarkable:

 

Complex Screen:
No compression:       768 bytes
RLE compressed:       403 bytes
Smart RLE compressed: 253 bytes    

Simple Screen:
No compression:       768 bytes
RLE compressed:       110 bytes
Smart RLE compressed:  21 bytes!

 

The "Smart" RLE approach requires that I implement both the RLE compressor I described earlier, and new decoration exporter that will detect placed objects on the screen (in Magellan) and output the required data structure, but the payoff is worth it.

 

Link to comment
Share on other sites

My goal for this game is to have the ability to deliver it via a physical cartridge, play it on a real TI, without requiring the memory expansion module (just like the OG games from back in the day). So I'm trying to understand the memory model I'll need to use to allow for that, but could use some advice in this regard.

 

I believe I can store all my static data (map templates, decoration definitions, etc.) in GROM format and load it as required as each screen is visited. But can I execute my program logic directly from cartridge ROM?  Of course I'll need the 256-byte scratchpad for storing the game state, but I should also be able to use a bunch of the VDP ram storage I'll have left over after char, sprite defs, correct?  My understanding is accessing the VDP ram is quite slow, but is it feasible to use it this way? What considerations should I be making in order to target the pure cartridge + stock TI99 deployment model?

 

Link to comment
Share on other sites

Returning to the original subject: I haven't found an easy and well documented example of passing parameters from XB to AL and back, for example CALL ADD(x,5,result) .. I learned there are 6 types of parameters and there is some kind of stack. 

 

Can someone provide an example or an explanation? 

 

Thank you

Steve

Link to comment
Share on other sites

On 12/23/2022 at 4:35 PM, retrodroid said:

To answer my own question, I had a look at the Magellan source code and it's implementing a code-type RLE scheme which uses the MSB of a character byte to indicate a repeating sequence. That is why only 128 chars can be used.  

I think I added that compression method for TI-Scramble where I could only use 128 characters anyway. That's the nature of a lot of the features I have added to Magellan: I add them for a specific project of mine, and if I think they are general enough for someone else to use, I include them in the master source. If you submit a pull request with your more general algorithm, I would be happy to merge it in.

21 hours ago, retrodroid said:

Well, it seems the Subject of this thread isn't really relevant to my current plans for this project. Should I change it and use this as my project thread, or start a new one?

If it's about Magellan, I think you should post in that thread, and if it's about assembly in general, there is also a thread about that. Otherwise it's your thread so feel free to dedicate it to your project (you could even change the title) or make another.

  • Like 1
Link to comment
Share on other sites

On 12/25/2022 at 3:08 PM, SteveB said:

Returning to the original subject: I haven't found an easy and well documented example of passing parameters from XB to AL and back, for example CALL ADD(x,5,result) .. I learned there are 6 types of parameters and there is some kind of stack. 

 

Can someone provide an example or an explanation? 

The "official" way is to use the provided utilities NUMREF, NUMASG for numerical reference and assignment, and STRREF, STRASG for strings. CFI and CIF are used to convert floating point to integer and back.

Look at page 23 in this scan of the Swedish newsletter Programbiten. It's only the assembly program, but the calls are explained in the comments. The comments are in English, so no problem there. Note that the daisy wheel used to print the newsletter replaced @ with ', so when you read BLWP 'STRREF it really means BLWP @STRREF.

The other way is to use CALL PEEK and CALL LOAD to read and store certain memory locations. But then you need to define these and keep them fixed (usually).

  • Like 1
Link to comment
Share on other sites

On 12/25/2022 at 8:08 AM, SteveB said:

Returning to the original subject: I haven't found an easy and well documented example of passing parameters from XB to AL and back, for example CALL ADD(x,5,result) .. I learned there are 6 types of parameters and there is some kind of stack. 

 

Can someone provide an example or an explanation? 

 

Thank you

Steve

Everything you need to know about this topic is in this document.

TI Extended BASIC Assembly Language Code Programmer_s Guide.pdf

  • Like 2
Link to comment
Share on other sites

53 minutes ago, Vorticon said:

Everything you need to know about this topic is in this document.

Thank you, I read this document (awful scan to read), but even if everything is in there, it is hard to understand. I also browsed through the E/A Manual. To get a first understanding the Assembly language primer from Thierry was most helpful. If you happen to read German, the "HAGERA ASSEMBLER KURS III" has the most detailed explanation I found to use Assembler in XB programs. With this I was able to understand much of your KPSUP.txt and started my first test program. A simple, well commented alas not very useful example-code. First I wanted to make a CALL and return without crashing anything. In the second iteration I wanted to put a character on the screen. And the third iteration finally got a parameter, which character to display, which I want to share with anyone having the same problem: Something small and well-documented to play with.

 

       DEF  CPUT     Export name of routine to be called

VSBW   EQU  >2020    Write single character to VRAM
VMBW   EQU  >2024    Write multiple characters to VRAM
NUMREF EQU  >200C    Gets a numeric parameter
NUMASG EQU  >2008    Makes a numeric assignment
STRREF EQU  >2014    Gets a string parameter.
STRASG EQU  >2010    Makes a string assignment.
XMLLNK EQU  >2018    Links to the assembly language routines in the console
CFI    EQU  >12B8    Convert Floating Point to Integer with XMLLNK
CIF    EQU  >20      Convert Integer to Floating Point with XMLLNK
FAC    EQU  >834A    Floating Point Accumulator

RETADR BSS  8        Reserve 8 bytes to save registers

CPUT   MOV  R11,@RETADR       Store registers R11, R13, R14, R15
       MOV  R13,@RETADR+2
       MOV  R14,@RETADR+4
       MOV  R15,@RETADR+6

       CLR  @FAC              Clear Floating Point Accumulator
       LI   R1,1              set R1 for reading the first parameter
       CLR  R0                set R2 to zero for single value (no array)
       BLWP @NUMREF           read first parameter in FAC
       BLWP @XMLLNK           generic console assembly routine
       DATA CFI                 - CFI Converts FAC from RADIX-100 to Word
       MOV @FAC,R1            Store first parameter (character-code) in R1
       AI R1,>60              VRAM charachters are offsetted >60 from ASCII
       SWPB R1                Swap bytes to get charachter number in Hi-Byte
       LI R0,>0080            Set R1 to the screen Position (row 4, col 1)
       BLWP @VSBW             Output character to screen

EXIT   MOV  @RETADR,R11       Restore registers before returning 
       MOV  @RETADR+2,R13
       MOV  @RETADR+4,R14
       MOV  @RETADR+6,R15
       RT                     Return to caller

       END

 

 I hope you don't mind copying your save/restore lines for the registers, I really liked them.

 

If anyone wants to start playing with it in Classic99: Copy the text to an editor, save it as CPUT.TXT, assemble it with Asm994a.exe included in the compiler package under Contributors\Harry_Wilhelm\ and store the obj-file on a FIAD disk, i.e. DSK1. Use CALL INIT when using XB 1.1 first. 

 

Then copy this XB program to Classic99

100 CALL CLEAR
110 CALL LOAD("DSK1.CPUT.OBJ")
120 CALL LINK("CPUT",64)

 

I will continue to explore the possibilities based on this tiny program...

 

  • Like 1
Link to comment
Share on other sites

20 minutes ago, apersson850 said:

In this program, they have no purpose at all.

I know ... but this is my playground and template ... and other things I already tried changed R11 when I BL'ed ... others to follow. 

 

Talking of not messing up the XB environment ... can someone tell me when I need to clear the GPL status at >837C before returning? And when do I need to need the CALL LOAD("DSK1.BSCSUP") ? 
 

  • Like 1
Link to comment
Share on other sites

Sure, they'll do no harm. But one of the ideas behind going to assembly is efficiency, in speed and memory use, and in that case you don't want to add eight instructions that have no purpose.

 

As far at I remember you need to clear >837C if the condition bit is set when you return. If you return with that bit set, the BASIC interpreter will assume you return an error condition.

Since testing the condition bit is more complicated than just clearing it, you clear it whether it's set or not, and then you are good to go.

 

Since the topic here is mixing assembly with Extended BASIC, the answer to when you need to load BSCSUP is: never. It's used if you call from TI BASIC, with the Editor/Assembler module plugged in (otherwise you can't) and you want to use the routines STRREF, NUMASG etcetera.

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

1 hour ago, SteveB said:

Talking of not messing up the XB environment ... can someone tell me when I need to clear the GPL status at >837C before returning?

 

Usually you only need to bother clearing the GPL status byte just before you call a GPLLNK routine, but @apersson850’s point about clearing it before returning to XB is well taken.

 

...lee

  • Like 1
Link to comment
Share on other sites

32 minutes ago, apersson850 said:

Sure, they'll do no harm. But one of the ideas behind going to assembly is efficiency, in speed and memory use, and in that case you don't want to add eight instructions that have no purpose.

Currently it is about learning for me, not speed. Yet. 

 

And if speed is the matter, I won't BLWP @VSBW to write a byte to VRAM either ...

 

Crawl. Walk. Run... I'm in phase one concerning TMS9900 assembly. 

 

Thanks for the BSCSUP clarification .. it's hard to follow up what's for TI BASIC and XB or not XB. Or MiniMem. 

 

  • Like 1
Link to comment
Share on other sites

Alright, at least you know why you do it. I've seen some who save return addresses obviously without knowing why, and hence they don't know when they need to do it either.

 

When it comes to BLWP @VSBW, there's at least a clean interface and no worries about which registers it uses to gain, compared to handling it all by yourself.

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

On 12/15/2022 at 11:21 PM, Lee Stewart said:

 

I have used EA, asm994a, and xa99 (in that order). I like asm994a, but it has its quirks and is no longer maintained by its developer. For those reasons, I moved on to xa99—and I am glad I did. The developer, Ralph Benzinger ( @ralphb ) is ever-present with help, fixes, and improvements. There are a handful of others of us here on AtariAge who are using xa99 and can offer assistance along your journey through Assembler.

 

...lee

Once I moved from asm994a to xas99 I didn't look back. For Stevie (a 64K cartridge) I have 8 banks that I assemble in parallel by using a bash batch file.

https://github.com/FilipVanVooren/stevie/blob/master/build/assemble.sh

https://github.com/FilipVanVooren/stevie/blob/master/build/build.sh

 

Edited by retroclouds
  • Like 4
Link to comment
Share on other sites

21 hours ago, apersson850 said:

As far at I remember you need to clear >837C if the condition bit is set when you return. If you return with that bit set, the BASIC interpreter will assume you return an error condition.

Since testing the condition bit is more complicated than just clearing it, you clear it whether it's set or not, and then you are good to go.

To me, what seems like the best way to return back to XB from the assembly sub is to:

LWPI >83E0     (if you are using a different workspace)

B @>006A      which clears the condition bit and returns to XB

If you think you might like to compile the XB code and use the A/L support then you must return to XB this way. B *R11 will not work.

Edited by senior_falcon
  • Like 3
Link to comment
Share on other sites

On 12/29/2022 at 5:36 AM, apersson850 said:

Because he saves R11, R13, R14 and R15 to begin with, and then restores them at the end. But he does nothing with them in between, so when he restores them, they already hold the same values as he puts back.

Got it. I have always thought these registers got altered by the XB assembly utility routines. Is this not the case?

  • Like 1
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...
 Share

  • Recently Browsing   0 members

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