Jump to content
IGNORED

Large project- memory considerations


Recommended Posts

My assembly project (MAC/65) is growing to massive size, to the point where I need to start at $8500 otherwise I can not debug in DDT while coding.

The project consists of a large main file with all app specific code and a utils file that has common macros and functions.

 

If I break the large main file into a few smaller ones, will it make any difference? After all, the assembler loads (#include) all files into memory regardless while assembling.

 

Is there a clever way to overcome the tight memory? Is there a way to use my memory expansion?

Link to comment
Share on other sites

Are you limiting yourself by developing on real h/w only? Cross-developing under an emulators debugger would be more efficient but I appreciate people want to enjoy how it was done originally.

Edited by Wrathchild
Link to comment
Share on other sites

8 minutes ago, Wrathchild said:

Are you limiting yourself by developing on real h/w only? Cross-developing under an emulators debugger would be more efficient but I appreciate people want to enjoy how it was done originally.

Yeah, I code on an Atari 800XL, like when I started so long ago. Its so fun to pop in a real floppy and load my code 🙂

For me, I work better under limitations as it forces me to make choices early on.

  • Like 1
Link to comment
Share on other sites

Overlays? Break the code into a main module you keep in main memory at all times plus overlay modules that you read in from disk or copy from the memory expansion as needed. Kind of sort of like a bank switched cart

  • Like 2
Link to comment
Share on other sites

You mention Macros, you know that each time you use one it's the entire macro code that's inserted, sometimes

it's easier to create a subroutine if it's used many times to reduce memory usage

 

  • Like 1
Link to comment
Share on other sites

Well, I recommend using my Cubbyhole technique.  It states that you can use the portions of memory allocated to the OS that are not being used during the course of your program for your code and data.  This can buy a couple k space for your program at the cost of a routine that loads in the stubs.  I do this for some cc65 programs, but if your assembler supports overlays, it should be doable there as well.  If you don't have one, you need a memory map of both the Atari and your particular DOS to tell you which addresses can be used.  Does this help?

  • Like 1
Link to comment
Share on other sites

Hi,

 

  I'm a bit rusty on MAC/65, and am not clear on exactly what you mean by "starting at $8500", but MAC/65 lets you assemble from disk, so you could have 10 small files all included into a MAIN.ASM file, and assemble with something like:

 

ASM #D1:MAIN.ASM,,#D1:MAIN.OBJ

 

See: https://atariwiki.org/wiki/attach/Mac65/MAC65 Assembler.pdf for details.

 

You'd have to edit/save each individual file, which might get a bit tedious, though a Ramdisk might help, given you have a memory expansion. I don't know whether an OS level debugger like Omnimon is an option, I have a SysCheck II, and one of the 4 flashable OS's is QMEG OS, which includes a monitor/debugger, and has been very handy at times.

 

  • Like 1
Link to comment
Share on other sites

BTW, if you have a few k free and require a lot of disk space, you might want to try compressing your overlays.  Right now, I only know about Exomizer, but I'm working on my own compression techniques and plan to make some of them available for multi-part programs that are too large to fit on one disk image.

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

On 6/22/2024 at 8:28 PM, yetanothertroll said:

Overlays? Break the code into a main module you keep in main memory at all times plus overlay modules that you read in from disk or copy from the memory expansion as needed. Kind of sort of like a bank switched cart

Any code samples on how to switch banks and access what's in the extended banks?

Link to comment
Share on other sites

You can assemble from a source file on disk without need to have it resident in memory.

In that case the only low memory used would be by Dos and the work area needed for the assembly process.

 

But yeah, the nostalgia thing might have it's attractions but there can be plenty of pain involved.

PC based development can mean a program change and run/test in a few seconds rather than several minutes.

  • Like 1
Link to comment
Share on other sites

1 hour ago, whomper said:

Any code samples on how to switch banks and access what's in the extended banks?

 

Without a cartridge inserted the bank values are

$FF Normal memory

BANKFLAG1 .BYTE $E3,$E7,$EB,$EF ; Bank values for PORTB

 

I use this code to switch banks in a disk copy program.

 

    ; switch to next RAM bank
BANK     LDX BANKFLAG
         LDA BANKFLAG1,X
         STA PORTB
         CPX #4
         BEQ T3
         INX 
         STX BANKFLAG
         LDA #0
         RTS 
T3       LDA #$FF

          STA PORTB
         RTS 

BANKFLAG .BYTE 0

BANKFLAG1 .BYTE $E3,$E7,$EB,$EF ; Bank values for PORTB

 

Memory is accesses through locations $4000 to $7FFF

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

If you have a U1M you can get the bank values from SDX, I used them in a demo I wrote last year

in cc65, I think I used 19 banks for the demo, but here's a list of the PORTB values for each bank

// rambank vaues for portb
char bank[]=
{0xFF,0xE3,0xE7,0xEB,0xEF,
0xE1,0xE5,0xE9,0xED,
0xC3,0xC7,0xCB,0xCF,
0xC1,0xC5,0xC9,0xCD,
0xA3,0xA7,0xAB,0xAF,
0xA1,0xA5,0xA9,0xAD,
0x83,0x87,0x8B,0x8F,
0x81,0x85,0x89,0x8D,
0x63,0x67,0x6B,0x6F,
0x61,0x65,0x69,0x6D,
0x43,0x47,0x4B,0x4F,
0x41,0x45,0x49,0x4D,
0x23,0x27,0x2B,0x2F,
0x21,0x25,0x29,0x2D,
0x03,0x07,0x0B,0x0F,0x01};

 

 

You can get this information using MEMINFO on SDX

Edited by TGB1718
Link to comment
Share on other sites

TGB1718: If you want, I have a cc65 memory extension for the Atari 1200XL that gives you an extra 64k memory and allows you to use the RAM that's allocated to the OS but unused during the course of most programs.  It is called MemXAtari and available at c65 additions - Manage /memory cfgs at SourceForge.net.  I also have AtaDisk65, which provides disk access that's more efficient than the standard cc65 file I/O library, but it's currently limited to opening, reading, writing and closing files but provides the ability to read files into memory easily.  It can be found at c65 additions - Manage /ui at SourceForge.net.  AtaSimpleIO is also there: it provides a very efficient replacement of functions such as printf() and puts() that interfaces directly with the OS and performs very little extra processing.  Try them out and tell me what you think.

Link to comment
Share on other sites

With the best will in the world Harry, I've looked at your utilities many times and have yet to find

any decent documentation on how to use any of your libraries/code.

For example I had a look at MemXAtari_001.txt hoping to see explanations and examples, but all that's there is:-

 

MemXAtari is a library for cc65 that allows easy access to the Atati XL's extra
RAM from a cc65 C program.  Also provides access to unused sections of Low
RAM for your cc65 program.  Uses my AtaDisk65 library.  Requires an Atari
1200XL.

 

So I had a look in some .C code, it's full of code commented out, sometimes more of that than

useable code and dare I say it you've used GOTO in C code 🤬 Oh Dear !!!

 

14 minutes ago, Harry Potter said:

Atari 1200XL that gives you an extra 64k

Where is this extra 64K ? 1200XL only has 64K as far as I know :)

 

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

I'm sorry about the lack of documentation.  At least I tried.  I'm pretty sure it was the 1200XL that supported the extra memory, as the library only worked when my emulator was in that mode.  Maybe I was thinking about another computer?  I have to clean up the code when I get a chance.

Link to comment
Share on other sites

5 hours ago, whomper said:

Any code samples on how to switch banks and access what's in the extended banks?

 

Sorry, not a clue. But between bank switched carts and extended memory, something like IBM PC DOS code overlays could be done

 

https://ftp.zx.net.nz/pub/Patches/ftp.microsoft.com/MISC/KB/en-us/51/416.HTM

 

4 hours ago, Rybags said:

You can assemble from a source file on disk without need to have it resident in memory.

In that case the only low memory used would be by Dos and the work area needed for the assembly process.

 

But yeah, the nostalgia thing might have it's attractions but there can be plenty of pain involved.

PC based development can mean a program change and run/test in a few seconds rather than several minutes.

 

Well that just leads into the nostalgic coffee breaks, right?

  • Like 1
Link to comment
Share on other sites

I started a new draft of MemXAtari then checked the header file and found out why there are very few comments there: the function names mimic the functions on which they are based.  For example, aux_strlen() will return the length of the string at the given address in aux memory.  Some functions also have from, to or in as suffixes.  From means aux memory is the source, to aux memory is the destination and in means from aux memory to aux memory.  After that, I need to fix all the code.  BTW, the reason I use goto in C is I sometimes want to transfer control from one part of a function to another, and a break or continue won't reach.  That, and it's there.  :)

Link to comment
Share on other sites

2 hours ago, Harry Potter said:

That, and it's there.  :)

Yes, appreciate that, but I think since I started using C many, many moons ago and maybe millions of lines of code

I don't think I ever used a goto :)

 

I made a small mod to one of your examples that used a goto, just to give you an idea on how easy it is

not to use goto. This is from main.c, I left the start label so you can see where I started 👍

 

Not having a "dig", just trying to be helpful 😇

 

void main ()
{
    printscr ("Loading stubs...");
    cio_load (2, "D1:XBUF1.BIN", 0x3FD, 0x303);
    loadauxmem ("D1:AUX1.BIN");

start:
    do
    {
        clrscr ();
        printscr (     " Welcome to MadLibAtari by Joseph Rose,\n"
             "        a.k.a. Harry Potter!\n"
             "---------------------------------------\n"
             "Please choose the category of a desired\n"
             "story:\n\n"

             "a.  Science Fiction\n"
             "b.  Fantasy\n"
             "c.  Generic\n\n"
             "x.  Exit"
             );
        cate=getkey();

        if (cate=='x') {
            printscr("Bye!  I hope you enjoyed this program.\n"
                "If you like it, please send me an e-mail"
                "at rose.joseph12@yahoo.com.");
            getkey();
            return;
        }

        cate-='a'; 
        if (cate>=3)
            illegalcate ();
        else
            dispsto2 ();

    }while(1);
}

 

Link to comment
Share on other sites

The current orthodoxy in the academic ivory towers is that break, continue, multiple return points, Exit If/Do/For/While, etc., are all just as bad as goto or setjmp/longjmp and will longjmp you right out of the course or even college. In the real world however, goto is almost mandatory to be certain all resources are cleaned up properly before the one and only one return point, especially in the case of other functions returning errors,

 

https://stackoverflow.com/questions/72581672/why-are-goto-break-continue-and-multiple-return-statements-considered-bad-prac

  • Like 1
Link to comment
Share on other sites

3 hours ago, Harry Potter said:

I think I used goto there because I found it to be more efficient.  I think that happened on another program also.  :)

 

2 hours ago, Wrathchild said:

In what way? How did the generated assembly differ?

It's pretty well known that even though structured programming can do everything goto spaghetti code can do, sometimes the structured approach needs more variables or redundant computations to accomplish the same tasks. For example, this (prototype, unoptimized!) FastBasic code has redundant comparisons all over the place, some to skip the Do/Loop, others to figure out exactly why that same Do/Loop exited. A few strategic Gotos would definitely shave off a few CPU cycles!

 

Proc malloc mbufsize

 If 0 = MFlags(0) Then @MSetup

 Print "malloc "; mbufsize; " ";

 ' Convert the requested buffer size to words
 mbufsize = (((mbufsize + 1) & $FFFE) / 2) & $7FFF

 Print mbufsize; " "; mbufsize * 2;

 If MemLo & $8000
  Print " regular"
  ' @free has been used so look for a free block that might be big enough
  mp = MemLo & $7FFF
  MemLo = mp ' ...that may or may not still exist
 Else
  Print " Express"
  ' There should be no free blocks to check so jump to extending the heap
  mp = MemHi
 EndIf

 Do
  ' Are we at the top of the heap already?
  If mp = MemHi Then Exit

  msz = MHeap(mp) ' Size of this block in words minus two byte header

  If msz & $8000

   ' This block has been flagged as having been freed

   MemLo = (MemLo ! $8000) ' Flag that freed blocks exist

   msz = msz & $7FFF

   If mbufsize <= msz Then Exit ' It's big enough to satisfy the request

   ' Otherwise, can it be merged with a following free block?
   mnp = mp + msz + 1

   If mnp = MemHi
    ' This free block is at the top of the heap with nothing else
    ' after it, so just pretend it doesn't exist and shrink the heap
    MemHi = mp

   ElIf (MHeap(mnp) & $8000) = 0
    ' The following block is in use, so skip to the block after it
    mp = mnp + MHeap(mnp) + 1

   Else
    ' Merge the free blocks, check the size in the next iteration
    MHeap(mp) = ((msz + (MHeap(mnp) & $7FFF) + 1) ! $8000)
   EndIf

  Else
   ' Block in use, move to the next one
    mp = mp + msz + 1
  EndIf
 Loop

 If 0 = mbufsize
  ' @malloc 0, full of sound and fury, signifying nothing
  mbufadr = 0

 ElIf mp = MemHi

  ' No free block was big enough, so try to extend the heap
  MNewHi = mp + mbufsize + 1

  If MNewHi <= MemMax
   MHeap(mp) = mbufsize
   MemHi = MNewHi
   mbufadr = &MHeap + (mp * 2) + 2

  Else
   Print "malloc out of heap "; mbufsize; " "; MNewHi; " "; MemMax
   . Get X
   mbufadr = 0
  EndIf

 ElIf mbufsize < msz
  ' The block is bigger than it needs to be, so split it into two
  MHeap(mp) = mbufsize
  MHeap(mp + mbufsize + 1) = ((msz - mbufsize - 1) ! $8000)
  mbufadr = &MHeap + (mp * 2) + 2

 ElIf mbufsize = msz
  ' Goldilocks says the block is already just right
  MHeap(mp) = msz
  mbufadr = &MHeap + (mp * 2) + 2

 Else
  ' We should never get here!
  mbufadr = 0
 EndIf

EndProc

 

Link to comment
Share on other sites

On 6/24/2024 at 5:58 AM, TGB1718 said:

...

 

Where is this extra 64K ? 1200XL only has 64K as far as I know :)

 

There may have been some bank switched expanded memory scheme. I'd check out the Altirra Hardware Reference Manual sections 2.6 and 2.7

Link to comment
Share on other sites

25 minutes ago, yetanothertroll said:

For example

Question was asked of Harry as I know he uses CC65.

 

Simple example:

 

static int myVar = 0;

void myFunc1(void)
{
	do
	{
		myVar++;
	} while (1);
}

void myFunc2(void)
{
myLabel:
	myVar++;
	goto myLabel;
}

void main()
{
	myFunc1();
	myFunc2();
}

 

Generates same code:

;
; File generated by cc65 v 2.13.2
;
	.fopt		compiler,"cc65 v 2.13.2"
	.setcpu		"6502"
	.smart		on
	.autoimport	on
	.case		on
	.debuginfo	off
	.importzp	sp, sreg, regsave, regbank, tmp1, ptr1, ptr2
	.macpack	longbranch
	.forceimport	__STARTUP__
	.export		_myFunc1
	.export		_myFunc2
	.export		_main

.segment	"DATA"

_myVar:
	.word	$0000

; ---------------------------------------------------------------
; void __near__ myFunc1 (void)
; ---------------------------------------------------------------

.segment	"CODE"

.proc	_myFunc1: near

.segment	"CODE"

L0004:	lda     _myVar
	ldx     _myVar+1
	jsr     incax1
	sta     _myVar
	stx     _myVar+1
	jmp     L0004

.endproc

; ---------------------------------------------------------------
; void __near__ myFunc2 (void)
; ---------------------------------------------------------------

.segment	"CODE"

.proc	_myFunc2: near

.segment	"CODE"

L000A:	lda     _myVar
	ldx     _myVar+1
	jsr     incax1
	sta     _myVar
	stx     _myVar+1
	jmp     L000A

.endproc

; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------

.segment	"CODE"

.proc	_main: near

.segment	"CODE"

	jsr     _myFunc1
	jmp     _myFunc2

.endproc

 

So I was curious to see where goto was more efficient

Link to comment
Share on other sites

8 minutes ago, yetanothertroll said:

There may have been some bank switched expanded memory scheme. I'd check out the Altirra Hardware Reference Manual sections 2.6 and 2.7

I understand the bank switching schemes, but Harry implied the 1200XL has an extra 64K which it doesn't.

The max RAM available on a 64K machine is 62K if you use the RAM that's "under" the ROM, without having a memory expansion fitted

 

On 6/24/2024 at 1:43 PM, Harry Potter said:

I have a cc65 memory extension for the Atari 1200XL that gives you an extra 64k memory and allows you to use the RAM that's allocated to the OS but unused during the course of most programs. 

 

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