11

According to cc65 / ca65 documentation, the .org directive does not affect the placement of the code unlike with other 6502 assemblers. The docs suggest defining a memory segment with the intended address instead.

Following the instructions there I created an extended .cfg file based on c64-asm.cfg that defines a new memory area HIMEM and a new segment HIMEMSEG:

  FEATURES {
      STARTADDRESS: default = $0801;
  }
  SYMBOLS {
      __LOADADDR__: type = import;
  }
  MEMORY {
      ZP:       file = "", start = $0002,  size = $00FE,      define = yes;
      LOADADDR: file = %O, start = %S - 2, size = $0002;
      MAIN:     file = %O, start = %S,     size = $D000 - %S;
      HIMEM:    file = %O, start = $c000,  size = $D000 - $c000; #this line was added
  }
  SEGMENTS {
      ZEROPAGE: load = ZP,       type = zp,  optional = yes;
      LOADADDR: load = LOADADDR, type = ro;
      EXEHDR:   load = MAIN,     type = ro,  optional = yes;
      CODE:     load = MAIN,     type = rw;
      RODATA:   load = MAIN,     type = ro,  optional = yes;
      DATA:     load = MAIN,     type = rw,  optional = yes;
      BSS:      load = MAIN,     type = bss, optional = yes, define = yes;
      HIMEMSEG: load = HIMEM,    type = ro;   #this line was added
  }

The new segment HIMEMSEG was then referenced in the code:

    .export prg1,prg2
    prg1:
            lda #$41
            jsr $ffd2
            jmp prg1

    .segment "HIMEMSEG"

    prg2:
            lda #$42
            jsr $ffd2
            jmp prg2

I assembled for target C64 with the command cl65 -t c64 -C c64x-asm.cfg prg.s -Ln labels.txt -o prg.prg and would have expected to get a large program with code parts at $0801and $c000. Interestingly, the result is a file that places all code closely together:

  .C:0801  A9 41       LDA #$41
  .C:0803  20 D2 FF    JSR $FFD2
  .C:0806  4C 01 08    JMP $0801
  .C:0809  A9 42       LDA #$42
  .C:080b  20 D2 FF    JSR $FFD2
  .C:080e  4C 00 C0    JMP $C000

Note the reference to $C000 t address 080f, also the exported labels in labels.txt indicate the code was meant to be placed in the defined HIMEMSEG ($C000). However, the linker just added those parts one after the other, producing an executable with the second part of the code being at the wrong address.

I have already searched existing forums for solutions, without success. I'm grateful for any suggestions on how to effectively putting code into two different memory areas with cc65. I want to be able to refer to the other code parts via .export / .import so building and loading the parts seperately is not an option.

5
  • 1
    FWIW, the .ORG statement actually does work like it does in many other assemblers. You often still want a .CFG file though. If you want to see how cc65 compares to other cross-assemblers, see this test data, particularly 2008-address-changes_*. Note the segment directives are commented out. (If you've used 64tass, .org works more like .logical than * =.) In any event, code should be relocated by the app loader or (if your OS doesn't do that) by the app's init code.
    – fadden
    Dec 27, 2019 at 15:43
  • Can you tell us a little more about what you're trying to achieve here, especially regarding the load process? I see what you want the code to look like in memory, but why do you want it to look like that and whence does the code get loaded?
    – cjs
    Dec 27, 2019 at 23:43
  • A practical example would be where the second part contains .incbin commands to include various sprite data - these should be placed well aligned at a predefined memory area where the VIC can address them. The main program should know the start address of the sprites to be able to run an animation. So we need forced placement by the linker and the exported addresses from the second part.
    – Peter B.
    Dec 28, 2019 at 0:43
  • For example, with the tassassembler, I would have just written *=$2000 spritelbl1 .binary "sprites1.prg",2 spritelbl2 .binary "sprites2.prg",2 and the assembler would have placed the sprites to $2000 and following while the labels spritelbl1 and spritelbl2 can be used in the main program to refer to the address where the sprite sequences are placed.
    – Peter B.
    Dec 28, 2019 at 0:48
  • 1
    Most cross-assemblers have an alignment directive that will do you what you need for sprites. You don't want or need a fixed address; you just need it to be 64-byte aligned (and then you reference #addr/64). For 64tass you would write .align 64 and let the assembler worry about picking an address. Because ca65 is designed to create relocatable object modules for compiler output it's a bit more awkward -- the .align directive depends on the alignment of the full segment (see docs).
    – fadden
    Dec 28, 2019 at 16:16

2 Answers 2

5

(Caveat: This is from memory with a quick peek into the manual. So some reply after trying it might be useful.)

TL;DR;

Fill it

Try fill=yes (*1) for the memory area of MAIN and make HIMEMSEG overwrite the area you added:

MEMORY {
    ZP:       file = "", start = $0002,  size = $00FE,      define = yes;
    LOADADDR: file = %O, start = %S - 2, size = $0002;
    MAIN:     file = %O, start = %S,     size = $D000 - %S, fill = yes;
}
SEGMENTS {
    ...
    HIMEMSEG: load = MAIN, start= $C000, type = overwrite;
}

This will fill the whole MAIN segment up to its end ($D000) and then put HIMEMSEG at it's location within.

Load it

Then again a better way would be using two files here. One holding the main program and another to hold HIMEM, to be loaded under program control:

  HIMEM: file = "%O-1.ovl", start = $C000,  size = $D000 - $C000;

This will move whatever code is (really) in HIMEM into a file with the same name plus a "-1.ovl" postfix (*2) like "prg-1.ovl" in your case. Of course, this needs some more startup code - but at the same time offers a good flexibility.

Move it

A more compact and at the same time more versatile way would be of course to add a loader of it's own. By adding a 'define = yes' to all segments containing data the linker will supply and populate three symbols for wach segment with load address, desired run address and size. Best solution, needs astonishingly little amount to be done but still to much to be covered here in full. IIRC there is a source example given with CC65.


General Note

It's always good to keep in mind that all these tools are about taking work from the programmer, while allowing to do whatever is needed. The most important step is here always to use abstract definitions that allow independent handling as long as possible. As a programmer sou should not have to care were your program is loaded or in which sequence - unless it's absolute essential. And even in such case the exact values should be postponed and managed separate - that's what the cc65 suite does and the configuration files are to handle this at the latest stage, during linking.

Background

While it's imaginable where your idea come from, it's as well something that can only occur to people living today .. after all, it would take close to two minutes to load 50 Kib ($D000-$800) - that's 200 blocks - from a standard 1541. All for a program that would fit into 1-2 blocks and load in 2-3 seconds. On machines back then there is a quite huge pressure to save on I/O transfer. Each KiB transferred less equals to about 2.5 seconds faster loading.

But beside the C64 limitations it's as well the way a linker operates (and this is a linker - LD65 - issue). A linker is supposed to prepare all segments into a file to be loaded. This is not necessarily a memory image, but a loadable image. Loading is not part of the linkers job, but to be done by an OS.

LD65 links what is given to a continuous file. Segments are only outputted with whatever they contain, not what they may contain. LD65 is a very versatile utility, but restricted to it's job. The next stage, the loaders job, is responsible to arrange the linked binary data into memory.

Where in most (past primitive) systems a loader exists that translates file layout into memory layout,a system like the C64 doesn't have such a loader. Only a simple load facility that moves a file into memory and starts execution.

Here it's up to the user to add the desired layer here. Either by splitting the program up into several files and manage the loading under program control, or by adding a small loader translating the file layout to a memory layout.


*1 - Optional with a filval= with whatever you like to see it filled.

7
  • Thanks for the profound answer and explanation! I tried out several possibilities, however none appear to be very satisfactory (refering to the situation, not your answer): * The fill method works as desribed, but generates a 202 blocks program. Also the filling always goes to the end of the memory block specified as MAIN, regardess of the actual end address of the last code line.
    – Peter B.
    Dec 27, 2019 at 22:49
  • * Multiple files appear to be a better approach, however, the written fiels are not annotated with a loading address, which means a possible loading program would need to get the information of the start address of each segment, possibly by parsing the cfg file? * Another approach would be to compile program parts completely seperated. However, I'm not aware of a simple possibilty to export symbols from one linking result to the other linking process. Writing a parser for the label file would be an option though.
    – Peter B.
    Dec 27, 2019 at 22:50
  • In overall, it is surprising to me that a thing that can be easily done with the .org command in other assemblers is so hard to emulate within ca65 / cl65.
    – Peter B.
    Dec 27, 2019 at 22:50
  • Well, wasn't a 202 block program what you wanted? Shaving 15 blocks off there doesn't make a big difference. You can change this by making the HIMEM a seperate memory block right next to Main, but that would be an uggly hack. Also, a 'simple' org won't help you anywhere. To make it work like you desire it would produce exactly the same 200 block program. After all you don't want a full memory image of your C64, but have a certain placement of your code chunks when loaded -) and that's the issue here: The C64 Kernal does not offer a loader able to obey such a structure.
    – Raffzahn
    Dec 27, 2019 at 23:17
  • it can simply load a single chunk of data from a file, not distribute it according to your wish. That's what a real loader is for. In case of the C64 it has to come with your program - kind of bootstraping your application into memory. Doing so you don't have to parse the config file, as CA65 can do everything by having the linker inserting appropriate values as part of linking. It can be used as a single in place solution, as mentioned in section 'move it', or utilized to create loader headers for the seperate files. Whatever is prefered (I usually would go with the all in one version)
    – Raffzahn
    Dec 27, 2019 at 23:25
1

The solution is to declare the MAIN memory region's size so it ends at the start of the HIMEM memory region and then add fill=yes to MAIN so it get's filled. This will have the same result as using * = $c000 in Turbo Assembler or 64TASS and similar assemblers:

FEATURES {
    STARTADDRESS: default = $0801;
}
SYMBOLS {
    __LOADADDR__: type = import;
}
MEMORY {
    ZP:       file = "", start = $0002,  size = $00FE,      define = yes;
    LOADADDR: file = %O, start = %S - 2, size = $0002;
    MAIN:     file = %O, start = %S,     size = $C000 - %S, fill=yes;  # Changed
    HIMEM:    file = %O, start = $C000,  size = $D000 - $C000;
}
SEGMENTS {
    ZEROPAGE: load = ZP,       type = zp,  optional = yes;
    LOADADDR: load = LOADADDR, type = ro;
    EXEHDR:   load = MAIN,     type = ro,  optional = yes;
    CODE:     load = MAIN,     type = rw;
    RODATA:   load = MAIN,     type = ro,  optional = yes;
    DATA:     load = MAIN,     type = rw,  optional = yes;
    BSS:      load = MAIN,     type = bss, optional = yes, define = yes;
    HIMEMSEG: load = HIMEM,    type = ro;
}

After loading, the second part can be found at the correct address:

.dc000-
.> c000 a9 42     lda #$42
.> c002 20 d2 ff  jsr $ffd2
.> c005 4c 00 c0  jmp $c000
.> c008 ff        ???
.> c009 ff        ???
.> c00a 00        brk
.> c00b 00        brk

And there's the typical pattern of $FF and $00 bytes after power up after the code, so there is no empty extra space after the code in the file.

Yes that is a huge file, but that's the way it works on the C64. The typical next step back then, and also today, is to compress the file. That doesn't just ”get rid” of all the zero bytes between the two (or more) memory regions but also compresses your sprite data and code.

One popular cross plattform compression program is Exomizer which can produce self decompressing executables for the C64 and several other 6502 based systems. The command line exomizer sfx 0xc000 prg.prg -o final.prg produces a 2 block final.prg file with a basic line that starts the decompressing and then jumps to $C000. It prints lots of 'B's.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .